This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
"""Code related to handling annotations."""
|
||||
|
||||
import sys
|
||||
import types
|
||||
import typing
|
||||
from inspect import isclass
|
||||
|
||||
|
||||
def is_none_type(value: typing.Any) -> bool:
|
||||
"""Check if the given value is a NoneType."""
|
||||
if sys.version_info < (3, 10):
|
||||
# raise Exception('below 3.10', value, type(None))
|
||||
return value is type(None)
|
||||
return value == types.NoneType # type: ignore[no-any-return]
|
||||
|
||||
|
||||
def get_optional_arg(annotation: typing.Any) -> typing.Any:
|
||||
"""Get the argument from an Optional[...] annotation, or None if it is no such annotation."""
|
||||
origin = typing.get_origin(annotation)
|
||||
if origin != typing.Union and (sys.version_info >= (3, 10) and origin != types.UnionType):
|
||||
return None
|
||||
|
||||
union_args = typing.get_args(annotation)
|
||||
if len(union_args) != 2: # Union does _not_ have two members, so it's not an Optional
|
||||
return None
|
||||
|
||||
has_none_arg = any(is_none_type(arg) for arg in union_args)
|
||||
# There will always be at least one type arg, as we have already established that this is a Union with exactly
|
||||
# two members, and both cannot be None (`Union[None, None]` does not work).
|
||||
type_arg = next(arg for arg in union_args if not is_none_type(arg)) # pragma: no branch
|
||||
|
||||
if has_none_arg:
|
||||
return type_arg
|
||||
return None
|
||||
|
||||
|
||||
def annotation_is_class(annotation: typing.Any) -> bool:
|
||||
"""Test if a given annotation is a class that can be used in isinstance()/issubclass()."""
|
||||
# isclass() returns True for generic type hints (e.g. `list[str]`) until Python 3.10.
|
||||
# NOTE: The guard for Python 3.9 is because types.GenericAlias is only added in Python 3.9. This is not a problem
|
||||
# as the syntax is added in the same version in the first place.
|
||||
if (3, 9) <= sys.version_info < (3, 11) and isinstance(annotation, types.GenericAlias):
|
||||
return False
|
||||
return isclass(annotation)
|
||||
|
||||
|
||||
def annotation_issubclass(annotation: typing.Any, cls: type) -> bool:
|
||||
"""Test if a given annotation is of the given subclass."""
|
||||
return annotation_is_class(annotation) and issubclass(annotation, cls)
|
||||
@@ -595,7 +595,8 @@ class LimitedSet:
|
||||
break # oldest item hasn't expired yet
|
||||
self.pop()
|
||||
|
||||
def pop(self, default: Any = None) -> Any:
|
||||
def pop(self, default=None) -> Any:
|
||||
# type: (Any) -> Any
|
||||
"""Remove and return the oldest item, or :const:`None` when empty."""
|
||||
while self._heap:
|
||||
_, item = heappop(self._heap)
|
||||
|
||||
@@ -54,9 +54,6 @@ def _boundmethod_safe_weakref(obj):
|
||||
def _make_lookup_key(receiver, sender, dispatch_uid):
|
||||
if dispatch_uid:
|
||||
return (dispatch_uid, _make_id(sender))
|
||||
# Issue #9119 - retry-wrapped functions use the underlying function for dispatch_uid
|
||||
elif hasattr(receiver, '_dispatch_uid'):
|
||||
return (receiver._dispatch_uid, _make_id(sender))
|
||||
else:
|
||||
return (_make_id(receiver), _make_id(sender))
|
||||
|
||||
@@ -173,7 +170,6 @@ class Signal: # pragma: no cover
|
||||
# it up later with the original func id
|
||||
options['dispatch_uid'] = _make_id(fun)
|
||||
fun = _retry_receiver(fun)
|
||||
fun._dispatch_uid = options['dispatch_uid']
|
||||
|
||||
self._connect_signal(fun, sender, options['weak'],
|
||||
options['dispatch_uid'])
|
||||
|
||||
@@ -51,13 +51,8 @@ def instantiate(name, *args, **kwargs):
|
||||
@contextmanager
|
||||
def cwd_in_path():
|
||||
"""Context adding the current working directory to sys.path."""
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except FileNotFoundError:
|
||||
cwd = None
|
||||
if not cwd:
|
||||
yield
|
||||
elif cwd in sys.path:
|
||||
cwd = os.getcwd()
|
||||
if cwd in sys.path:
|
||||
yield
|
||||
else:
|
||||
sys.path.insert(0, cwd)
|
||||
|
||||
@@ -50,9 +50,9 @@ TIMEZONE_REGEX = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def parse_iso8601(datestring: str) -> datetime:
|
||||
def parse_iso8601(datestring):
|
||||
"""Parse and convert ISO-8601 string to datetime."""
|
||||
warn("parse_iso8601", "v5.3", "v6", "datetime.datetime.fromisoformat or dateutil.parser.isoparse")
|
||||
warn("parse_iso8601", "v5.3", "v6", "datetime.datetime.fromisoformat")
|
||||
m = ISO8601_REGEX.match(datestring)
|
||||
if not m:
|
||||
raise ValueError('unable to parse date string %r' % datestring)
|
||||
|
||||
@@ -37,7 +37,7 @@ base_logger = logger = _get_logger('celery')
|
||||
|
||||
|
||||
def set_in_sighandler(value):
|
||||
"""Set flag signifying that we're inside a signal handler."""
|
||||
"""Set flag signifiying that we're inside a signal handler."""
|
||||
global _in_sighandler
|
||||
_in_sighandler = value
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""Worker name utilities."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import socket
|
||||
from functools import partial
|
||||
@@ -24,18 +22,13 @@ NODENAME_DEFAULT = 'celery'
|
||||
gethostname = memoize(1, Cache=dict)(socket.gethostname)
|
||||
|
||||
__all__ = (
|
||||
'worker_direct',
|
||||
'gethostname',
|
||||
'nodename',
|
||||
'anon_nodename',
|
||||
'nodesplit',
|
||||
'default_nodename',
|
||||
'node_format',
|
||||
'host_format',
|
||||
'worker_direct', 'gethostname', 'nodename',
|
||||
'anon_nodename', 'nodesplit', 'default_nodename',
|
||||
'node_format', 'host_format',
|
||||
)
|
||||
|
||||
|
||||
def worker_direct(hostname: str | Queue) -> Queue:
|
||||
def worker_direct(hostname):
|
||||
"""Return the :class:`kombu.Queue` being a direct route to a worker.
|
||||
|
||||
Arguments:
|
||||
@@ -53,20 +46,21 @@ def worker_direct(hostname: str | Queue) -> Queue:
|
||||
)
|
||||
|
||||
|
||||
def nodename(name: str, hostname: str) -> str:
|
||||
def nodename(name, hostname):
|
||||
"""Create node name from name/hostname pair."""
|
||||
return NODENAME_SEP.join((name, hostname))
|
||||
|
||||
|
||||
def anon_nodename(hostname: str | None = None, prefix: str = 'gen') -> str:
|
||||
def anon_nodename(hostname=None, prefix='gen'):
|
||||
"""Return the nodename for this process (not a worker).
|
||||
|
||||
This is used for e.g. the origin task message field.
|
||||
"""
|
||||
return nodename(''.join([prefix, str(os.getpid())]), hostname or gethostname())
|
||||
return nodename(''.join([prefix, str(os.getpid())]),
|
||||
hostname or gethostname())
|
||||
|
||||
|
||||
def nodesplit(name: str) -> tuple[None, str] | list[str]:
|
||||
def nodesplit(name):
|
||||
"""Split node name into tuple of name/hostname."""
|
||||
parts = name.split(NODENAME_SEP, 1)
|
||||
if len(parts) == 1:
|
||||
@@ -74,21 +68,21 @@ def nodesplit(name: str) -> tuple[None, str] | list[str]:
|
||||
return parts
|
||||
|
||||
|
||||
def default_nodename(hostname: str) -> str:
|
||||
def default_nodename(hostname):
|
||||
"""Return the default nodename for this process."""
|
||||
name, host = nodesplit(hostname or '')
|
||||
return nodename(name or NODENAME_DEFAULT, host or gethostname())
|
||||
|
||||
|
||||
def node_format(s: str, name: str, **extra: dict) -> str:
|
||||
def node_format(s, name, **extra):
|
||||
"""Format worker node name (name@host.com)."""
|
||||
shortname, host = nodesplit(name)
|
||||
return host_format(s, host, shortname or NODENAME_DEFAULT, p=name, **extra)
|
||||
return host_format(
|
||||
s, host, shortname or NODENAME_DEFAULT, p=name, **extra)
|
||||
|
||||
|
||||
def _fmt_process_index(prefix: str = '', default: str = '0') -> str:
|
||||
def _fmt_process_index(prefix='', default='0'):
|
||||
from .log import current_process_index
|
||||
|
||||
index = current_process_index()
|
||||
return f'{prefix}{index}' if index else default
|
||||
|
||||
@@ -96,19 +90,13 @@ def _fmt_process_index(prefix: str = '', default: str = '0') -> str:
|
||||
_fmt_process_index_with_prefix = partial(_fmt_process_index, '-', '')
|
||||
|
||||
|
||||
def host_format(s: str, host: str | None = None, name: str | None = None, **extra: dict) -> str:
|
||||
def host_format(s, host=None, name=None, **extra):
|
||||
"""Format host %x abbreviations."""
|
||||
host = host or gethostname()
|
||||
hname, _, domain = host.partition('.')
|
||||
name = name or hname
|
||||
keys = dict(
|
||||
{
|
||||
'h': host,
|
||||
'n': name,
|
||||
'd': domain,
|
||||
'i': _fmt_process_index,
|
||||
'I': _fmt_process_index_with_prefix,
|
||||
},
|
||||
**extra,
|
||||
)
|
||||
keys = dict({
|
||||
'h': host, 'n': name, 'd': domain,
|
||||
'i': _fmt_process_index, 'I': _fmt_process_index_with_prefix,
|
||||
}, **extra)
|
||||
return simple_format(s, keys)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def detect_quorum_queues(app, driver_type: str) -> tuple[bool, str]:
|
||||
"""Detect if any of the queues are quorum queues.
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: A tuple containing a boolean indicating if any of the queues are quorum queues
|
||||
and the name of the first quorum queue found or an empty string if no quorum queues were found.
|
||||
"""
|
||||
is_rabbitmq_broker = driver_type == 'amqp'
|
||||
|
||||
if is_rabbitmq_broker:
|
||||
queues = app.amqp.queues
|
||||
for qname in queues:
|
||||
qarguments = queues[qname].queue_arguments or {}
|
||||
if qarguments.get("x-queue-type") == "quorum":
|
||||
return True, qname
|
||||
|
||||
return False, ""
|
||||
@@ -15,7 +15,7 @@ from decimal import Decimal
|
||||
from itertools import chain
|
||||
from numbers import Number
|
||||
from pprint import _recursion
|
||||
from typing import Any, AnyStr, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple # noqa
|
||||
from typing import Any, AnyStr, Callable, Dict, Iterator, List, Sequence, Set, Tuple # noqa
|
||||
|
||||
from .text import truncate
|
||||
|
||||
@@ -41,7 +41,7 @@ _quoted = namedtuple('_quoted', ('value',))
|
||||
#: Recursion protection.
|
||||
_dirty = namedtuple('_dirty', ('objid',))
|
||||
|
||||
#: Types that are represented as chars.
|
||||
#: Types that are repsented as chars.
|
||||
chars_t = (bytes, str)
|
||||
|
||||
#: Types that are regarded as safe to call repr on.
|
||||
@@ -194,12 +194,9 @@ def _reprseq(val, lit_start, lit_end, builtin_type, chainer):
|
||||
)
|
||||
|
||||
|
||||
def reprstream(stack: deque,
|
||||
seen: Optional[Set] = None,
|
||||
maxlevels: int = 3,
|
||||
level: int = 0,
|
||||
isinstance: Callable = isinstance) -> Iterator[Any]:
|
||||
def reprstream(stack, seen=None, maxlevels=3, level=0, isinstance=isinstance):
|
||||
"""Streaming repr, yielding tokens."""
|
||||
# type: (deque, Set, int, int, Callable) -> Iterator[Any]
|
||||
seen = seen or set()
|
||||
append = stack.append
|
||||
popleft = stack.popleft
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""System information utilities."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from math import ceil
|
||||
|
||||
@@ -11,16 +9,16 @@ __all__ = ('load_average', 'df')
|
||||
|
||||
if hasattr(os, 'getloadavg'):
|
||||
|
||||
def _load_average() -> tuple[float, ...]:
|
||||
def _load_average():
|
||||
return tuple(ceil(l * 1e2) / 1e2 for l in os.getloadavg())
|
||||
|
||||
else: # pragma: no cover
|
||||
# Windows doesn't have getloadavg
|
||||
def _load_average() -> tuple[float, ...]:
|
||||
return 0.0, 0.0, 0.0,
|
||||
def _load_average():
|
||||
return (0.0, 0.0, 0.0)
|
||||
|
||||
|
||||
def load_average() -> tuple[float, ...]:
|
||||
def load_average():
|
||||
"""Return system load average as a triple."""
|
||||
return _load_average()
|
||||
|
||||
@@ -28,23 +26,23 @@ def load_average() -> tuple[float, ...]:
|
||||
class df:
|
||||
"""Disk information."""
|
||||
|
||||
def __init__(self, path: str | bytes | os.PathLike) -> None:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
@property
|
||||
def total_blocks(self) -> float:
|
||||
def total_blocks(self):
|
||||
return self.stat.f_blocks * self.stat.f_frsize / 1024
|
||||
|
||||
@property
|
||||
def available(self) -> float:
|
||||
def available(self):
|
||||
return self.stat.f_bavail * self.stat.f_frsize / 1024
|
||||
|
||||
@property
|
||||
def capacity(self) -> int:
|
||||
def capacity(self):
|
||||
avail = self.stat.f_bavail
|
||||
used = self.stat.f_blocks - self.stat.f_bfree
|
||||
return int(ceil(used * 100.0 / (used + avail) + 0.5))
|
||||
|
||||
@cached_property
|
||||
def stat(self) -> os.statvfs_result:
|
||||
def stat(self):
|
||||
return os.statvfs(os.path.abspath(self.path))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Terminals and colors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
@@ -9,8 +8,6 @@ from functools import reduce
|
||||
|
||||
__all__ = ('colored',)
|
||||
|
||||
from typing import Any
|
||||
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
||||
OP_SEQ = '\033[%dm'
|
||||
RESET_SEQ = '\033[0m'
|
||||
@@ -29,7 +26,7 @@ _IMG_PRE = '\033Ptmux;\033\033]' if TERM_IS_SCREEN else '\033]'
|
||||
_IMG_POST = '\a\033\\' if TERM_IS_SCREEN else '\a'
|
||||
|
||||
|
||||
def fg(s: int) -> str:
|
||||
def fg(s):
|
||||
return COLOR_SEQ % s
|
||||
|
||||
|
||||
@@ -44,11 +41,11 @@ class colored:
|
||||
... c.green('dog ')))
|
||||
"""
|
||||
|
||||
def __init__(self, *s: object, **kwargs: Any) -> None:
|
||||
self.s: tuple[object, ...] = s
|
||||
self.enabled: bool = not IS_WINDOWS and kwargs.get('enabled', True)
|
||||
self.op: str = kwargs.get('op', '')
|
||||
self.names: dict[str, Any] = {
|
||||
def __init__(self, *s, **kwargs):
|
||||
self.s = s
|
||||
self.enabled = not IS_WINDOWS and kwargs.get('enabled', True)
|
||||
self.op = kwargs.get('op', '')
|
||||
self.names = {
|
||||
'black': self.black,
|
||||
'red': self.red,
|
||||
'green': self.green,
|
||||
@@ -59,10 +56,10 @@ class colored:
|
||||
'white': self.white,
|
||||
}
|
||||
|
||||
def _add(self, a: object, b: object) -> str:
|
||||
return f"{a}{b}"
|
||||
def _add(self, a, b):
|
||||
return str(a) + str(b)
|
||||
|
||||
def _fold_no_color(self, a: Any, b: Any) -> str:
|
||||
def _fold_no_color(self, a, b):
|
||||
try:
|
||||
A = a.no_color()
|
||||
except AttributeError:
|
||||
@@ -72,113 +69,109 @@ class colored:
|
||||
except AttributeError:
|
||||
B = str(b)
|
||||
|
||||
return f"{A}{B}"
|
||||
return ''.join((str(A), str(B)))
|
||||
|
||||
def no_color(self) -> str:
|
||||
def no_color(self):
|
||||
if self.s:
|
||||
return str(reduce(self._fold_no_color, self.s))
|
||||
return ''
|
||||
|
||||
def embed(self) -> str:
|
||||
def embed(self):
|
||||
prefix = ''
|
||||
if self.enabled:
|
||||
prefix = self.op
|
||||
return f"{prefix}{reduce(self._add, self.s)}"
|
||||
return ''.join((str(prefix), str(reduce(self._add, self.s))))
|
||||
|
||||
def __str__(self) -> str:
|
||||
def __str__(self):
|
||||
suffix = ''
|
||||
if self.enabled:
|
||||
suffix = RESET_SEQ
|
||||
return f"{self.embed()}{suffix}"
|
||||
return str(''.join((self.embed(), str(suffix))))
|
||||
|
||||
def node(self, s: tuple[object, ...], op: str) -> colored:
|
||||
def node(self, s, op):
|
||||
return self.__class__(enabled=self.enabled, op=op, *s)
|
||||
|
||||
def black(self, *s: object) -> colored:
|
||||
def black(self, *s):
|
||||
return self.node(s, fg(30 + BLACK))
|
||||
|
||||
def red(self, *s: object) -> colored:
|
||||
def red(self, *s):
|
||||
return self.node(s, fg(30 + RED))
|
||||
|
||||
def green(self, *s: object) -> colored:
|
||||
def green(self, *s):
|
||||
return self.node(s, fg(30 + GREEN))
|
||||
|
||||
def yellow(self, *s: object) -> colored:
|
||||
def yellow(self, *s):
|
||||
return self.node(s, fg(30 + YELLOW))
|
||||
|
||||
def blue(self, *s: object) -> colored:
|
||||
def blue(self, *s):
|
||||
return self.node(s, fg(30 + BLUE))
|
||||
|
||||
def magenta(self, *s: object) -> colored:
|
||||
def magenta(self, *s):
|
||||
return self.node(s, fg(30 + MAGENTA))
|
||||
|
||||
def cyan(self, *s: object) -> colored:
|
||||
def cyan(self, *s):
|
||||
return self.node(s, fg(30 + CYAN))
|
||||
|
||||
def white(self, *s: object) -> colored:
|
||||
def white(self, *s):
|
||||
return self.node(s, fg(30 + WHITE))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
def __repr__(self):
|
||||
return repr(self.no_color())
|
||||
|
||||
def bold(self, *s: object) -> colored:
|
||||
def bold(self, *s):
|
||||
return self.node(s, OP_SEQ % 1)
|
||||
|
||||
def underline(self, *s: object) -> colored:
|
||||
def underline(self, *s):
|
||||
return self.node(s, OP_SEQ % 4)
|
||||
|
||||
def blink(self, *s: object) -> colored:
|
||||
def blink(self, *s):
|
||||
return self.node(s, OP_SEQ % 5)
|
||||
|
||||
def reverse(self, *s: object) -> colored:
|
||||
def reverse(self, *s):
|
||||
return self.node(s, OP_SEQ % 7)
|
||||
|
||||
def bright(self, *s: object) -> colored:
|
||||
def bright(self, *s):
|
||||
return self.node(s, OP_SEQ % 8)
|
||||
|
||||
def ired(self, *s: object) -> colored:
|
||||
def ired(self, *s):
|
||||
return self.node(s, fg(40 + RED))
|
||||
|
||||
def igreen(self, *s: object) -> colored:
|
||||
def igreen(self, *s):
|
||||
return self.node(s, fg(40 + GREEN))
|
||||
|
||||
def iyellow(self, *s: object) -> colored:
|
||||
def iyellow(self, *s):
|
||||
return self.node(s, fg(40 + YELLOW))
|
||||
|
||||
def iblue(self, *s: colored) -> colored:
|
||||
def iblue(self, *s):
|
||||
return self.node(s, fg(40 + BLUE))
|
||||
|
||||
def imagenta(self, *s: object) -> colored:
|
||||
def imagenta(self, *s):
|
||||
return self.node(s, fg(40 + MAGENTA))
|
||||
|
||||
def icyan(self, *s: object) -> colored:
|
||||
def icyan(self, *s):
|
||||
return self.node(s, fg(40 + CYAN))
|
||||
|
||||
def iwhite(self, *s: object) -> colored:
|
||||
def iwhite(self, *s):
|
||||
return self.node(s, fg(40 + WHITE))
|
||||
|
||||
def reset(self, *s: object) -> colored:
|
||||
return self.node(s or ('',), RESET_SEQ)
|
||||
def reset(self, *s):
|
||||
return self.node(s or [''], RESET_SEQ)
|
||||
|
||||
def __add__(self, other: object) -> str:
|
||||
return f"{self}{other}"
|
||||
def __add__(self, other):
|
||||
return str(self) + str(other)
|
||||
|
||||
|
||||
def supports_images() -> bool:
|
||||
|
||||
try:
|
||||
return sys.stdin.isatty() and bool(os.environ.get('ITERM_PROFILE'))
|
||||
except AttributeError:
|
||||
return False
|
||||
def supports_images():
|
||||
return sys.stdin.isatty() and ITERM_PROFILE
|
||||
|
||||
|
||||
def _read_as_base64(path: str) -> str:
|
||||
with open(path, mode='rb') as fh:
|
||||
def _read_as_base64(path):
|
||||
with codecs.open(path, mode='rb') as fh:
|
||||
encoded = base64.b64encode(fh.read())
|
||||
return encoded.decode('ascii')
|
||||
return encoded if isinstance(encoded, str) else encoded.decode('ascii')
|
||||
|
||||
|
||||
def imgcat(path: str, inline: int = 1, preserve_aspect_ratio: int = 0, **kwargs: Any) -> str:
|
||||
def imgcat(path, inline=1, preserve_aspect_ratio=0, **kwargs):
|
||||
return '\n%s1337;File=inline=%d;preserveAspectRatio=%d:%s%s' % (
|
||||
_IMG_PRE, inline, preserve_aspect_ratio,
|
||||
_read_as_base64(path), _IMG_POST)
|
||||
|
||||
@@ -14,7 +14,6 @@ from types import ModuleType
|
||||
from typing import Any, Callable
|
||||
|
||||
from dateutil import tz as dateutil_tz
|
||||
from dateutil.parser import isoparse
|
||||
from kombu.utils.functional import reprcall
|
||||
from kombu.utils.objects import cached_property
|
||||
|
||||
@@ -41,9 +40,6 @@ C_REMDEBUG = os.environ.get('C_REMDEBUG', False)
|
||||
DAYNAMES = 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
|
||||
WEEKDAYS = dict(zip(DAYNAMES, range(7)))
|
||||
|
||||
MONTHNAMES = 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'
|
||||
YEARMONTHS = dict(zip(MONTHNAMES, range(1, 13)))
|
||||
|
||||
RATE_MODIFIER_MAP = {
|
||||
's': lambda n: n,
|
||||
'm': lambda n: n / 60.0,
|
||||
@@ -204,7 +200,7 @@ def delta_resolution(dt: datetime, delta: timedelta) -> datetime:
|
||||
def remaining(
|
||||
start: datetime, ends_in: timedelta, now: Callable | None = None,
|
||||
relative: bool = False) -> timedelta:
|
||||
"""Calculate the real remaining time for a start date and a timedelta.
|
||||
"""Calculate the remaining time for a start date and a timedelta.
|
||||
|
||||
For example, "how many seconds left for 30 seconds after start?"
|
||||
|
||||
@@ -215,28 +211,24 @@ def remaining(
|
||||
using :func:`delta_resolution` (i.e., rounded to the
|
||||
resolution of `ends_in`).
|
||||
now (Callable): Function returning the current time and date.
|
||||
Defaults to :func:`datetime.now(timezone.utc)`.
|
||||
Defaults to :func:`datetime.utcnow`.
|
||||
|
||||
Returns:
|
||||
~datetime.timedelta: Remaining time.
|
||||
"""
|
||||
now = now or datetime.now(datetime_timezone.utc)
|
||||
now = now or datetime.utcnow()
|
||||
if str(
|
||||
start.tzinfo) == str(
|
||||
now.tzinfo) and now.utcoffset() != start.utcoffset():
|
||||
# DST started/ended
|
||||
start = start.replace(tzinfo=now.tzinfo)
|
||||
end_date = start + ends_in
|
||||
if relative:
|
||||
end_date = delta_resolution(end_date, ends_in).replace(microsecond=0)
|
||||
|
||||
# Using UTC to calculate real time difference.
|
||||
# Python by default uses wall time in arithmetic between datetimes with
|
||||
# equal non-UTC timezones.
|
||||
now_utc = now.astimezone(timezone.utc)
|
||||
end_date_utc = end_date.astimezone(timezone.utc)
|
||||
ret = end_date_utc - now_utc
|
||||
ret = end_date - now
|
||||
if C_REMDEBUG: # pragma: no cover
|
||||
print(
|
||||
'rem: NOW:{!r} NOW_UTC:{!r} START:{!r} ENDS_IN:{!r} '
|
||||
'END_DATE:{} END_DATE_UTC:{!r} REM:{}'.format(
|
||||
now, now_utc, start, ends_in, end_date, end_date_utc, ret)
|
||||
)
|
||||
print('rem: NOW:{!r} START:{!r} ENDS_IN:{!r} END_DATE:{} REM:{}'.format(
|
||||
now, start, ends_in, end_date, ret))
|
||||
return ret
|
||||
|
||||
|
||||
@@ -265,21 +257,6 @@ def weekday(name: str) -> int:
|
||||
raise KeyError(name)
|
||||
|
||||
|
||||
def yearmonth(name: str) -> int:
|
||||
"""Return the position of a month: 1 - 12, where 1 is January.
|
||||
|
||||
Example:
|
||||
>>> yearmonth('january'), yearmonth('jan'), yearmonth('may')
|
||||
(1, 1, 5)
|
||||
"""
|
||||
abbreviation = name[0:3].lower()
|
||||
try:
|
||||
return YEARMONTHS[abbreviation]
|
||||
except KeyError:
|
||||
# Show original day name in exception, instead of abbr.
|
||||
raise KeyError(name)
|
||||
|
||||
|
||||
def humanize_seconds(
|
||||
secs: int, prefix: str = '', sep: str = '', now: str = 'now',
|
||||
microseconds: bool = False) -> str:
|
||||
@@ -311,7 +288,7 @@ def maybe_iso8601(dt: datetime | str | None) -> None | datetime:
|
||||
return
|
||||
if isinstance(dt, datetime):
|
||||
return dt
|
||||
return isoparse(dt)
|
||||
return datetime.fromisoformat(dt)
|
||||
|
||||
|
||||
def is_naive(dt: datetime) -> bool:
|
||||
@@ -325,7 +302,7 @@ def _can_detect_ambiguous(tz: tzinfo) -> bool:
|
||||
return isinstance(tz, ZoneInfo) or hasattr(tz, "is_ambiguous")
|
||||
|
||||
|
||||
def _is_ambiguous(dt: datetime, tz: tzinfo) -> bool:
|
||||
def _is_ambigious(dt: datetime, tz: tzinfo) -> bool:
|
||||
"""Helper function to determine if a timezone is ambiguous using python's dateutil module.
|
||||
|
||||
Returns False if the timezone cannot detect ambiguity, or if there is no ambiguity, otherwise True.
|
||||
@@ -342,7 +319,7 @@ def make_aware(dt: datetime, tz: tzinfo) -> datetime:
|
||||
"""Set timezone for a :class:`~datetime.datetime` object."""
|
||||
|
||||
dt = dt.replace(tzinfo=tz)
|
||||
if _is_ambiguous(dt, tz):
|
||||
if _is_ambigious(dt, tz):
|
||||
dt = min(dt.replace(fold=0), dt.replace(fold=1))
|
||||
return dt
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import threading
|
||||
from itertools import count
|
||||
from threading import TIMEOUT_MAX as THREAD_TIMEOUT_MAX
|
||||
from time import sleep
|
||||
from typing import Any, Callable, Iterator, Optional, Tuple
|
||||
|
||||
from kombu.asynchronous.timer import Entry
|
||||
from kombu.asynchronous.timer import Timer as Schedule
|
||||
@@ -31,23 +30,20 @@ class Timer(threading.Thread):
|
||||
Entry = Entry
|
||||
Schedule = Schedule
|
||||
|
||||
running: bool = False
|
||||
on_tick: Optional[Callable[[float], None]] = None
|
||||
running = False
|
||||
on_tick = None
|
||||
|
||||
_timer_count: count = count(1)
|
||||
_timer_count = count(1)
|
||||
|
||||
if TIMER_DEBUG: # pragma: no cover
|
||||
def start(self, *args: Any, **kwargs: Any) -> None:
|
||||
def start(self, *args, **kwargs):
|
||||
import traceback
|
||||
print('- Timer starting')
|
||||
traceback.print_stack()
|
||||
super().start(*args, **kwargs)
|
||||
|
||||
def __init__(self, schedule: Optional[Schedule] = None,
|
||||
on_error: Optional[Callable[[Exception], None]] = None,
|
||||
on_tick: Optional[Callable[[float], None]] = None,
|
||||
on_start: Optional[Callable[['Timer'], None]] = None,
|
||||
max_interval: Optional[float] = None, **kwargs: Any) -> None:
|
||||
def __init__(self, schedule=None, on_error=None, on_tick=None,
|
||||
on_start=None, max_interval=None, **kwargs):
|
||||
self.schedule = schedule or self.Schedule(on_error=on_error,
|
||||
max_interval=max_interval)
|
||||
self.on_start = on_start
|
||||
@@ -64,10 +60,8 @@ class Timer(threading.Thread):
|
||||
self.daemon = True
|
||||
self.name = f'Timer-{next(self._timer_count)}'
|
||||
|
||||
def _next_entry(self) -> Optional[float]:
|
||||
def _next_entry(self):
|
||||
with self.not_empty:
|
||||
delay: Optional[float]
|
||||
entry: Optional[Entry]
|
||||
delay, entry = next(self.scheduler)
|
||||
if entry is None:
|
||||
if delay is None:
|
||||
@@ -76,10 +70,10 @@ class Timer(threading.Thread):
|
||||
return self.schedule.apply_entry(entry)
|
||||
__next__ = next = _next_entry # for 2to3
|
||||
|
||||
def run(self) -> None:
|
||||
def run(self):
|
||||
try:
|
||||
self.running = True
|
||||
self.scheduler: Iterator[Tuple[Optional[float], Optional[Entry]]] = iter(self.schedule)
|
||||
self.scheduler = iter(self.schedule)
|
||||
|
||||
while not self.__is_shutdown.is_set():
|
||||
delay = self._next_entry()
|
||||
@@ -100,61 +94,61 @@ class Timer(threading.Thread):
|
||||
sys.stderr.flush()
|
||||
os._exit(1)
|
||||
|
||||
def stop(self) -> None:
|
||||
def stop(self):
|
||||
self.__is_shutdown.set()
|
||||
if self.running:
|
||||
self.__is_stopped.wait()
|
||||
self.join(THREAD_TIMEOUT_MAX)
|
||||
self.running = False
|
||||
|
||||
def ensure_started(self) -> None:
|
||||
def ensure_started(self):
|
||||
if not self.running and not self.is_alive():
|
||||
if self.on_start:
|
||||
self.on_start(self)
|
||||
self.start()
|
||||
|
||||
def _do_enter(self, meth: str, *args: Any, **kwargs: Any) -> Entry:
|
||||
def _do_enter(self, meth, *args, **kwargs):
|
||||
self.ensure_started()
|
||||
with self.mutex:
|
||||
entry = getattr(self.schedule, meth)(*args, **kwargs)
|
||||
self.not_empty.notify()
|
||||
return entry
|
||||
|
||||
def enter(self, entry: Entry, eta: float, priority: Optional[int] = None) -> Entry:
|
||||
def enter(self, entry, eta, priority=None):
|
||||
return self._do_enter('enter_at', entry, eta, priority=priority)
|
||||
|
||||
def call_at(self, *args: Any, **kwargs: Any) -> Entry:
|
||||
def call_at(self, *args, **kwargs):
|
||||
return self._do_enter('call_at', *args, **kwargs)
|
||||
|
||||
def enter_after(self, *args: Any, **kwargs: Any) -> Entry:
|
||||
def enter_after(self, *args, **kwargs):
|
||||
return self._do_enter('enter_after', *args, **kwargs)
|
||||
|
||||
def call_after(self, *args: Any, **kwargs: Any) -> Entry:
|
||||
def call_after(self, *args, **kwargs):
|
||||
return self._do_enter('call_after', *args, **kwargs)
|
||||
|
||||
def call_repeatedly(self, *args: Any, **kwargs: Any) -> Entry:
|
||||
def call_repeatedly(self, *args, **kwargs):
|
||||
return self._do_enter('call_repeatedly', *args, **kwargs)
|
||||
|
||||
def exit_after(self, secs: float, priority: int = 10) -> None:
|
||||
def exit_after(self, secs, priority=10):
|
||||
self.call_after(secs, sys.exit, priority)
|
||||
|
||||
def cancel(self, tref: Entry) -> None:
|
||||
def cancel(self, tref):
|
||||
tref.cancel()
|
||||
|
||||
def clear(self) -> None:
|
||||
def clear(self):
|
||||
self.schedule.clear()
|
||||
|
||||
def empty(self) -> bool:
|
||||
def empty(self):
|
||||
return not len(self)
|
||||
|
||||
def __len__(self) -> int:
|
||||
def __len__(self):
|
||||
return len(self.schedule)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
def __bool__(self):
|
||||
"""``bool(timer)``."""
|
||||
return True
|
||||
__nonzero__ = __bool__
|
||||
|
||||
@property
|
||||
def queue(self) -> list:
|
||||
def queue(self):
|
||||
return self.schedule.queue
|
||||
|
||||
Reference in New Issue
Block a user