dycw-utilities 0.127.0__py3-none-any.whl → 0.129.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
utilities/asyncio.py CHANGED
@@ -1,9 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
- from abc import ABC, abstractmethod
5
3
  from asyncio import (
6
- CancelledError,
7
4
  Event,
8
5
  Lock,
9
6
  PriorityQueue,
@@ -19,7 +16,7 @@ from asyncio import (
19
16
  sleep,
20
17
  timeout,
21
18
  )
22
- from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
19
+ from collections.abc import Callable, Iterable, Iterator
23
20
  from contextlib import (
24
21
  AbstractAsyncContextManager,
25
22
  AsyncExitStack,
@@ -37,7 +34,6 @@ from typing import (
37
34
  TYPE_CHECKING,
38
35
  Any,
39
36
  Generic,
40
- NoReturn,
41
37
  Self,
42
38
  TextIO,
43
39
  TypeVar,
@@ -50,27 +46,19 @@ from typing_extensions import deprecated
50
46
 
51
47
  from utilities.dataclasses import replace_non_sentinel
52
48
  from utilities.datetime import (
53
- MINUTE,
54
49
  SECOND,
55
50
  datetime_duration_to_float,
56
- datetime_duration_to_timedelta,
57
51
  get_now,
58
52
  round_datetime,
59
53
  )
60
- from utilities.errors import ImpossibleCaseError, repr_error
61
- from utilities.functions import ensure_int, ensure_not_none, get_class_name
54
+ from utilities.errors import repr_error
55
+ from utilities.functions import ensure_int, ensure_not_none
62
56
  from utilities.random import SYSTEM_RANDOM
63
57
  from utilities.sentinel import Sentinel, sentinel
64
- from utilities.types import (
65
- Coroutine1,
66
- DurationOrEveryDuration,
67
- MaybeCallableEvent,
68
- MaybeType,
69
- THashable,
70
- TSupportsRichComparison,
71
- )
58
+ from utilities.types import MaybeCallableEvent, THashable, TSupportsRichComparison
72
59
 
73
60
  if TYPE_CHECKING:
61
+ import datetime as dt
74
62
  from asyncio import _CoroutineLike
75
63
  from asyncio.subprocess import Process
76
64
  from collections import deque
@@ -319,346 +307,6 @@ class EnhancedTaskGroup(TaskGroup):
319
307
  ##
320
308
 
321
309
 
322
- @dataclass(kw_only=True, unsafe_hash=True)
323
- class InfiniteLooper(ABC, Generic[THashable]):
324
- """An infinite loop which can throw exceptions by setting events."""
325
-
326
- sleep_core: DurationOrEveryDuration = field(default=SECOND, repr=False)
327
- sleep_restart: DurationOrEveryDuration = field(default=MINUTE, repr=False)
328
- duration: Duration | None = field(default=None, repr=False)
329
- logger: str | None = field(default=None, repr=False)
330
- _await_upon_aenter: bool = field(default=True, init=False, repr=False)
331
- _depth: int = field(default=0, init=False, repr=False)
332
- _events: Mapping[THashable | None, Event] = field(
333
- default_factory=dict, init=False, repr=False, hash=False
334
- )
335
- _stack: AsyncExitStack = field(
336
- default_factory=AsyncExitStack, init=False, repr=False
337
- )
338
- _task: Task[None] | None = field(default=None, init=False, repr=False)
339
-
340
- def __post_init__(self) -> None:
341
- self._events = {
342
- event: Event() for event, _ in self._yield_events_and_exceptions()
343
- }
344
-
345
- async def __aenter__(self) -> Self:
346
- """Context manager entry."""
347
- if self._depth == 0:
348
- self._task = create_task(self._run_looper())
349
- if self._await_upon_aenter:
350
- with suppress(CancelledError):
351
- await self._task
352
- _ = await self._stack.__aenter__()
353
- self._depth += 1
354
- return self
355
-
356
- async def __aexit__(
357
- self,
358
- exc_type: type[BaseException] | None = None,
359
- exc_value: BaseException | None = None,
360
- traceback: TracebackType | None = None,
361
- ) -> None:
362
- """Context manager exit."""
363
- _ = (exc_type, exc_value, traceback)
364
- self._depth = max(self._depth - 1, 0)
365
- if (self._depth == 0) and (self._task is not None):
366
- with suppress(CancelledError):
367
- await self._task
368
- self._task = None
369
- try:
370
- await self._teardown()
371
- except Exception as error: # noqa: BLE001
372
- self._error_upon_teardown(error)
373
- _ = await self._stack.__aexit__(exc_type, exc_value, traceback)
374
-
375
- async def stop(self) -> None:
376
- """Stop the service."""
377
- if self._task is None:
378
- raise ImpossibleCaseError(case=[f"{self._task=}"]) # pragma: no cover
379
- with suppress(CancelledError):
380
- _ = self._task.cancel()
381
-
382
- async def _run_looper(self) -> None:
383
- """Run the looper."""
384
- match self.duration:
385
- case None:
386
- await self._run_looper_without_timeout()
387
- case int() | float() | dt.timedelta() as duration:
388
- try:
389
- async with timeout_dur(duration=duration):
390
- return await self._run_looper_without_timeout()
391
- except TimeoutError:
392
- await self.stop()
393
- case _ as never:
394
- assert_never(never)
395
- return None
396
-
397
- async def _run_looper_without_timeout(self) -> None:
398
- """Run the looper without a timeout."""
399
- coroutines = list(self._yield_coroutines())
400
- loopers = list(self._yield_loopers())
401
- if (len(coroutines) == 0) and (len(loopers) == 0):
402
- return await self._run_looper_by_itself()
403
- return await self._run_looper_with_others(coroutines, loopers)
404
-
405
- async def _run_looper_by_itself(self) -> None:
406
- """Run the looper by itself."""
407
- whitelisted = tuple(self._yield_whitelisted_errors())
408
- blacklisted = tuple(self._yield_blacklisted_errors())
409
- while True:
410
- try:
411
- self._reset_events()
412
- try:
413
- await self._initialize()
414
- except Exception as error: # noqa: BLE001
415
- self._error_upon_initialize(error)
416
- await self._run_sleep(self.sleep_restart)
417
- else:
418
- while True:
419
- try:
420
- event = next(
421
- key
422
- for (key, value) in self._events.items()
423
- if value.is_set()
424
- )
425
- except StopIteration:
426
- await self._core()
427
- await self._run_sleep(self.sleep_core)
428
- else:
429
- self._raise_error(event)
430
- except InfiniteLooperError:
431
- raise
432
- except BaseException as error1:
433
- match error1:
434
- case Exception():
435
- if isinstance(error1, blacklisted):
436
- raise
437
- case BaseException():
438
- if not isinstance(error1, whitelisted):
439
- raise
440
- case _ as never:
441
- assert_never(never)
442
- self._error_upon_core(error1)
443
- try:
444
- await self._teardown()
445
- except BaseException as error2: # noqa: BLE001
446
- self._error_upon_teardown(error2)
447
- finally:
448
- await self._run_sleep(self.sleep_restart)
449
-
450
- async def _run_looper_with_others(
451
- self,
452
- coroutines: Iterable[Callable[[], Coroutine1[None]]],
453
- loopers: Iterable[InfiniteLooper[Any]],
454
- /,
455
- ) -> None:
456
- """Run multiple loopers."""
457
- while True:
458
- self._reset_events()
459
- try:
460
- async with TaskGroup() as tg, AsyncExitStack() as stack:
461
- _ = tg.create_task(self._run_looper_by_itself())
462
- _ = [tg.create_task(c()) for c in coroutines]
463
- _ = [
464
- tg.create_task(stack.enter_async_context(lo)) for lo in loopers
465
- ]
466
- except ExceptionGroup as error:
467
- self._error_group_upon_others(error)
468
- await self._run_sleep(self.sleep_restart)
469
-
470
- async def _initialize(self) -> None:
471
- """Initialize the loop."""
472
-
473
- async def _core(self) -> None:
474
- """Run the core part of the loop."""
475
-
476
- async def _teardown(self) -> None:
477
- """Tear down the loop."""
478
-
479
- def _error_upon_initialize(self, error: Exception, /) -> None:
480
- """Handle any errors upon initializing the looper."""
481
- if self.logger is not None:
482
- getLogger(name=self.logger).error(
483
- "%r encountered %r whilst initializing; sleeping %s...",
484
- get_class_name(self),
485
- repr_error(error),
486
- self._sleep_restart_desc,
487
- )
488
-
489
- def _error_upon_core(self, error: BaseException, /) -> None:
490
- """Handle any errors upon running the core function."""
491
- if self.logger is not None:
492
- getLogger(name=self.logger).error(
493
- "%r encountered %r; sleeping %s...",
494
- get_class_name(self),
495
- repr_error(error),
496
- self._sleep_restart_desc,
497
- )
498
-
499
- def _error_upon_teardown(self, error: BaseException, /) -> None:
500
- """Handle any errors upon tearing down the looper."""
501
- if self.logger is not None:
502
- getLogger(name=self.logger).error(
503
- "%r encountered %r whilst tearing down; sleeping %s...",
504
- get_class_name(self),
505
- repr_error(error),
506
- self._sleep_restart_desc,
507
- )
508
-
509
- def _error_group_upon_others(self, group: ExceptionGroup, /) -> None:
510
- """Handle any errors upon running the core function."""
511
- if self.logger is not None:
512
- errors = group.exceptions
513
- n = len(errors)
514
- msgs = [f"{get_class_name(self)!r} encountered {n} error(s):"]
515
- msgs.extend(
516
- f"- Error #{i}/{n}: {repr_error(e)}"
517
- for i, e in enumerate(errors, start=1)
518
- )
519
- msgs.append(f"Sleeping {self._sleep_restart_desc}...")
520
- getLogger(name=self.logger).error("\n".join(msgs))
521
-
522
- def _raise_error(self, event: THashable | None, /) -> NoReturn:
523
- """Raise the error corresponding to given event."""
524
- mapping = dict(self._yield_events_and_exceptions())
525
- error = mapping.get(event, InfiniteLooperError)
526
- raise error
527
-
528
- def _reset_events(self) -> None:
529
- """Reset the events."""
530
- self._events = {
531
- event: Event() for event, _ in self._yield_events_and_exceptions()
532
- }
533
-
534
- async def _run_sleep(self, sleep: DurationOrEveryDuration, /) -> None:
535
- """Sleep until the next part of the loop."""
536
- match sleep:
537
- case int() | float() | dt.timedelta() as duration:
538
- await sleep_dur(duration=duration)
539
- case "every", (int() | float() | dt.timedelta()) as duration:
540
- await sleep_until_rounded(duration)
541
- case _ as never:
542
- assert_never(never)
543
-
544
- @property
545
- def _sleep_restart_desc(self) -> str:
546
- """Get a description of the sleep until restart."""
547
- match self.sleep_restart:
548
- case int() | float() | dt.timedelta() as duration:
549
- timedelta = datetime_duration_to_timedelta(duration)
550
- return f"for {timedelta}"
551
- case "every", (int() | float() | dt.timedelta()) as duration:
552
- timedelta = datetime_duration_to_timedelta(duration)
553
- return f"until next {timedelta}"
554
- case _ as never:
555
- assert_never(never)
556
-
557
- def _set_event(self, *, event: THashable | None = None) -> None:
558
- """Set the given event."""
559
- try:
560
- event_obj = self._events[event]
561
- except KeyError:
562
- raise _InfiniteLooperNoSuchEventError(looper=self, event=event) from None
563
- event_obj.set()
564
-
565
- def _yield_events_and_exceptions(
566
- self,
567
- ) -> Iterator[tuple[THashable | None, MaybeType[Exception]]]:
568
- """Yield the events & exceptions."""
569
- yield (None, _InfiniteLooperDefaultEventError(looper=self))
570
-
571
- def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
572
- """Yield any other coroutines which must also be run."""
573
- yield from []
574
-
575
- def _yield_loopers(self) -> Iterator[InfiniteLooper[Any]]:
576
- """Yield any other loopers which must also be run."""
577
- yield from []
578
-
579
- def _yield_blacklisted_errors(self) -> Iterator[type[Exception]]:
580
- """Yield any exceptions which the looper ought to catch terminate upon."""
581
- yield from []
582
-
583
- def _yield_whitelisted_errors(self) -> Iterator[type[BaseException]]:
584
- """Yield any exceptions which the looper ought to catch and allow running."""
585
- yield from []
586
-
587
-
588
- @dataclass(kw_only=True, slots=True)
589
- class InfiniteLooperError(Exception):
590
- looper: InfiniteLooper[Any]
591
-
592
-
593
- @dataclass(kw_only=True, slots=True)
594
- class _InfiniteLooperNoSuchEventError(InfiniteLooperError):
595
- event: Hashable
596
-
597
- @override
598
- def __str__(self) -> str:
599
- return f"{get_class_name(self.looper)!r} does not have an event {self.event!r}"
600
-
601
-
602
- @dataclass(kw_only=True, slots=True)
603
- class _InfiniteLooperDefaultEventError(InfiniteLooperError):
604
- @override
605
- def __str__(self) -> str:
606
- return f"{get_class_name(self.looper)!r} default event error"
607
-
608
-
609
- ##
610
-
611
-
612
- @dataclass(kw_only=True)
613
- class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
614
- """An infinite loop which processes a queue."""
615
-
616
- _await_upon_aenter: bool = field(default=False, init=False, repr=False)
617
- _queue: EnhancedQueue[_T] = field(
618
- default_factory=EnhancedQueue, init=False, repr=False
619
- )
620
-
621
- def __len__(self) -> int:
622
- return self._queue.qsize()
623
-
624
- @override
625
- async def _core(self) -> None:
626
- """Run the core part of the loop."""
627
- if self.empty():
628
- return
629
- await self._process_queue()
630
-
631
- @abstractmethod
632
- async def _process_queue(self) -> None:
633
- """Process the queue."""
634
-
635
- def empty(self) -> bool:
636
- """Check if the queue is empty."""
637
- return self._queue.empty()
638
-
639
- def put_left_nowait(self, *items: _T) -> None:
640
- """Put items into the queue at the start without blocking."""
641
- self._queue.put_left_nowait(*items) # pragma: no cover
642
-
643
- def put_right_nowait(self, *items: _T) -> None:
644
- """Put items into the queue at the end without blocking."""
645
- self._queue.put_right_nowait(*items) # pragma: no cover
646
-
647
- def qsize(self) -> int:
648
- """Get the number of items in the queue."""
649
- return self._queue.qsize()
650
-
651
- async def run_until_empty(self, *, stop: bool = False) -> None:
652
- """Run until the queue is empty."""
653
- while not self.empty():
654
- await self._process_queue()
655
- if stop:
656
- await self.stop()
657
-
658
-
659
- ##
660
-
661
-
662
310
  @dataclass(kw_only=True, slots=True)
663
311
  class LooperError(Exception): ...
664
312
 
@@ -705,6 +353,7 @@ class Looper(Generic[_T]):
705
353
  _is_entered: Event = field(default_factory=Event, init=False, repr=False)
706
354
  _is_initialized: Event = field(default_factory=Event, init=False, repr=False)
707
355
  _is_initializing: Event = field(default_factory=Event, init=False, repr=False)
356
+ _is_pending_back_off: Event = field(default_factory=Event, init=False, repr=False)
708
357
  _is_pending_restart: Event = field(default_factory=Event, init=False, repr=False)
709
358
  _is_pending_stop: Event = field(default_factory=Event, init=False, repr=False)
710
359
  _is_pending_stop_when_empty: Event = field(
@@ -791,6 +440,11 @@ class Looper(Generic[_T]):
791
440
  def __len__(self) -> int:
792
441
  return self._queue.qsize()
793
442
 
443
+ async def _apply_back_off(self) -> None:
444
+ """Apply a back off period."""
445
+ await sleep(self._backoff)
446
+ self._is_pending_back_off.clear()
447
+
794
448
  async def core(self) -> None:
795
449
  """Core part of running the looper."""
796
450
 
@@ -810,7 +464,9 @@ class Looper(Generic[_T]):
810
464
  """Remove and return an item from the end of the queue without blocking."""
811
465
  return self._queue.get_right_nowait()
812
466
 
813
- async def initialize(self, *, sleep_if_failure: bool) -> Exception | None:
467
+ async def initialize(
468
+ self, *, skip_sleep_if_failure: bool = False
469
+ ) -> Exception | None:
814
470
  """Initialize the looper."""
815
471
  match self._is_initializing.is_set():
816
472
  case True:
@@ -828,21 +484,21 @@ class Looper(Generic[_T]):
828
484
  async with self._lock:
829
485
  self._initialization_failures += 1
830
486
  ret = error
831
- match sleep_if_failure:
487
+ match skip_sleep_if_failure:
832
488
  case True:
833
489
  _ = self._logger.warning(
834
- "%s: encountered %s whilst initializing; sleeping for %s...",
490
+ "%s: encountered %s whilst initializing",
835
491
  self,
836
492
  repr_error(error),
837
- self.backoff,
838
493
  )
839
- await sleep(self._backoff)
840
494
  case False:
841
495
  _ = self._logger.warning(
842
- "%s: encountered %s whilst initializing",
496
+ "%s: encountered %s whilst initializing; sleeping for %s...",
843
497
  self,
844
498
  repr_error(error),
499
+ self.backoff,
845
500
  )
501
+ await self._apply_back_off()
846
502
  case _ as never:
847
503
  assert_never(never)
848
504
  else:
@@ -899,6 +555,21 @@ class Looper(Generic[_T]):
899
555
  **kwargs,
900
556
  )
901
557
 
558
+ def request_back_off(self) -> None:
559
+ """Request the looper to back off."""
560
+ match self._is_pending_back_off.is_set():
561
+ case True:
562
+ _ = self._debug and self._logger.debug(
563
+ "%s: already requested back off", self
564
+ )
565
+ case False:
566
+ _ = self._debug and self._logger.debug(
567
+ "%s: requesting back off...", self
568
+ )
569
+ self._is_pending_back_off.set()
570
+ case _ as never:
571
+ assert_never(never)
572
+
902
573
  def request_restart(self) -> None:
903
574
  """Request the looper to restart."""
904
575
  match self._is_pending_restart.is_set():
@@ -913,6 +584,7 @@ class Looper(Generic[_T]):
913
584
  self._is_pending_restart.set()
914
585
  case _ as never:
915
586
  assert_never(never)
587
+ self.request_back_off()
916
588
 
917
589
  def request_stop(self) -> None:
918
590
  """Request the looper to stop."""
@@ -942,20 +614,20 @@ class Looper(Generic[_T]):
942
614
  case _ as never:
943
615
  assert_never(never)
944
616
 
945
- async def restart(self, *, sleep_if_failure: bool) -> None:
617
+ async def restart(self) -> None:
946
618
  """Restart the looper."""
947
619
  _ = self._debug and self._logger.debug("%s: restarting...", self)
948
620
  self._is_pending_restart.clear()
949
621
  async with self._lock:
950
622
  self._restart_attempts += 1
951
- tear_down = await self.tear_down(sleep_if_failure=False)
952
- initialization = await self.initialize(sleep_if_failure=False)
953
- match tear_down, initialization, sleep_if_failure:
954
- case None, None, bool():
623
+ tear_down = await self.tear_down(skip_sleep_if_failure=True)
624
+ initialization = await self.initialize(skip_sleep_if_failure=True)
625
+ match tear_down, initialization:
626
+ case None, None:
955
627
  _ = self._debug and self._logger.debug("%s: finished restarting", self)
956
628
  async with self._lock:
957
629
  self._restart_successes += 1
958
- case Exception(), None, True:
630
+ case Exception(), None:
959
631
  async with self._lock:
960
632
  self._restart_failures += 1
961
633
  _ = self._logger.warning(
@@ -964,16 +636,8 @@ class Looper(Generic[_T]):
964
636
  repr_error(tear_down),
965
637
  self.backoff,
966
638
  )
967
- await sleep(self._backoff)
968
- case Exception(), None, False:
969
- async with self._lock:
970
- self._restart_failures += 1
971
- _ = self._logger.warning(
972
- "%s: encountered %s whilst restarting (tear down)",
973
- self,
974
- repr_error(tear_down),
975
- )
976
- case None, Exception(), True:
639
+ await self._apply_back_off()
640
+ case None, Exception():
977
641
  async with self._lock:
978
642
  self._restart_failures += 1
979
643
  _ = self._logger.warning(
@@ -982,16 +646,8 @@ class Looper(Generic[_T]):
982
646
  repr_error(initialization),
983
647
  self.backoff,
984
648
  )
985
- await sleep(self._backoff)
986
- case None, Exception(), False:
987
- async with self._lock:
988
- self._restart_failures += 1
989
- _ = self._logger.warning(
990
- "%s: encountered %s whilst restarting (initialize)",
991
- self,
992
- repr_error(initialization),
993
- )
994
- case Exception(), Exception(), True:
649
+ await self._apply_back_off()
650
+ case Exception(), Exception():
995
651
  async with self._lock:
996
652
  self._restart_failures += 1
997
653
  _ = self._logger.warning(
@@ -1001,16 +657,7 @@ class Looper(Generic[_T]):
1001
657
  repr_error(initialization),
1002
658
  self.backoff,
1003
659
  )
1004
- await sleep(self._backoff)
1005
- case Exception(), Exception(), False:
1006
- async with self._lock:
1007
- self._restart_failures += 1
1008
- _ = self._logger.warning(
1009
- "%s: encountered %s (tear down) and then %s (initialization) whilst restarting",
1010
- self,
1011
- repr_error(tear_down),
1012
- repr_error(initialization),
1013
- )
660
+ await self._apply_back_off()
1014
661
  case _ as never:
1015
662
  assert_never(never)
1016
663
 
@@ -1026,10 +673,12 @@ class Looper(Generic[_T]):
1026
673
  self._is_pending_stop_when_empty.is_set() and self.empty()
1027
674
  ):
1028
675
  await self.stop()
676
+ elif self._is_pending_back_off.is_set():
677
+ await self._apply_back_off()
1029
678
  elif self._is_pending_restart.is_set():
1030
- await self.restart(sleep_if_failure=True)
679
+ await self.restart()
1031
680
  elif not self._is_initialized.is_set():
1032
- _ = await self.initialize(sleep_if_failure=True)
681
+ _ = await self.initialize()
1033
682
  else:
1034
683
  _ = self._debug and self._logger.debug(
1035
684
  "%s: running core...", self
@@ -1047,7 +696,6 @@ class Looper(Generic[_T]):
1047
696
  async with self._lock:
1048
697
  self._core_failures += 1
1049
698
  self.request_restart()
1050
- await sleep(self._backoff)
1051
699
  else:
1052
700
  async with self._lock:
1053
701
  self._core_successes += 1
@@ -1101,7 +749,9 @@ class Looper(Generic[_T]):
1101
749
  case _ as never:
1102
750
  assert_never(never)
1103
751
 
1104
- async def tear_down(self, *, sleep_if_failure: bool) -> Exception | None:
752
+ async def tear_down(
753
+ self, *, skip_sleep_if_failure: bool = False
754
+ ) -> Exception | None:
1105
755
  """Tear down the looper."""
1106
756
  match self._is_tearing_down.is_set():
1107
757
  case True:
@@ -1118,21 +768,21 @@ class Looper(Generic[_T]):
1118
768
  async with self._lock:
1119
769
  self._tear_down_failures += 1
1120
770
  ret = error
1121
- match sleep_if_failure:
771
+ match skip_sleep_if_failure:
1122
772
  case True:
1123
773
  _ = self._logger.warning(
1124
- "%s: encountered %s whilst tearing down; sleeping for %s...",
774
+ "%s: encountered %s whilst tearing down",
1125
775
  self,
1126
776
  repr_error(error),
1127
- self.backoff,
1128
777
  )
1129
- await sleep(self._backoff)
1130
778
  case False:
1131
779
  _ = self._logger.warning(
1132
- "%s: encountered %s whilst tearing down",
780
+ "%s: encountered %s whilst tearing down; sleeping for %s...",
1133
781
  self,
1134
782
  repr_error(error),
783
+ self.backoff,
1135
784
  )
785
+ await self._apply_back_off()
1136
786
  case _ as never:
1137
787
  assert_never(never)
1138
788
  else:
@@ -1415,9 +1065,6 @@ async def timeout_dur(
1415
1065
  __all__ = [
1416
1066
  "EnhancedQueue",
1417
1067
  "EnhancedTaskGroup",
1418
- "InfiniteLooper",
1419
- "InfiniteLooperError",
1420
- "InfiniteQueueLooper",
1421
1068
  "Looper",
1422
1069
  "LooperError",
1423
1070
  "StreamCommandOutput",
utilities/datetime.py CHANGED
@@ -509,14 +509,6 @@ def get_datetime(*, datetime: MaybeCallableDateTime) -> dt.datetime: ...
509
509
  def get_datetime(*, datetime: None) -> None: ...
510
510
  @overload
511
511
  def get_datetime(*, datetime: Sentinel) -> Sentinel: ...
512
- @overload
513
- def get_datetime(
514
- *, datetime: MaybeCallableDateTime | Sentinel
515
- ) -> dt.datetime | Sentinel: ...
516
- @overload
517
- def get_datetime(
518
- *, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
519
- ) -> dt.datetime | None | Sentinel: ...
520
512
  def get_datetime(
521
513
  *, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
522
514
  ) -> dt.datetime | None | Sentinel: