dycw-utilities 0.125.12__py3-none-any.whl → 0.125.14__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.125.12
3
+ Version: 0.125.14
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
- utilities/__init__.py,sha256=eu0r7qVMzjFJgG7Su5IuWbXETZTCy5nXfNr7kwpH_yw,61
1
+ utilities/__init__.py,sha256=FUi2dI2qq8_0UHJ0RQEHLRzzmOO3YY-SHiWkJPnb-P4,61
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
- utilities/asyncio.py,sha256=G7CaIcR-oANVjKWIH7KFMHpPZqL1CkwCV6FBT8vrkKA,46941
3
+ utilities/asyncio.py,sha256=UUcMb_QT4g4-EW0qIoiSENu8x6Fcjn5_X-vvbMrLBfE,50658
4
4
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
5
5
  utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
6
6
  utilities/cachetools.py,sha256=C1zqOg7BYz0IfQFK8e3qaDDgEZxDpo47F15RTfJM37Q,2910
@@ -88,7 +88,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
88
88
  utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
89
89
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
90
90
  utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
91
- dycw_utilities-0.125.12.dist-info/METADATA,sha256=9JECEW7LQjwHf5sjb53N4xApsa3V9RImfG__u2LDN4E,12852
92
- dycw_utilities-0.125.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.125.12.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.125.12.dist-info/RECORD,,
91
+ dycw_utilities-0.125.14.dist-info/METADATA,sha256=ptsWeHCzCc0Tmvlmzovy3NZ4PLYMSEeT8VdauAv3b0k,12852
92
+ dycw_utilities-0.125.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.125.14.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.125.14.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.125.12"
3
+ __version__ = "0.125.14"
utilities/asyncio.py CHANGED
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
5
5
  from asyncio import (
6
6
  CancelledError,
7
7
  Event,
8
+ Lock,
8
9
  PriorityQueue,
9
10
  Queue,
10
11
  QueueEmpty,
@@ -719,6 +720,7 @@ class Looper(Generic[_T]):
719
720
  _is_stopped: Event = field(default_factory=Event, init=False, repr=False)
720
721
  _is_tearing_down: Event = field(default_factory=Event, init=False, repr=False)
721
722
  # internal objects
723
+ _lock: Lock = field(default_factory=Lock, init=False, repr=False, hash=False)
722
724
  _logger: Logger = field(init=False, repr=False, hash=False)
723
725
  _queue: EnhancedQueue[_T] = field(
724
726
  default_factory=EnhancedQueue, init=False, repr=False, hash=False
@@ -742,8 +744,9 @@ class Looper(Generic[_T]):
742
744
  case False:
743
745
  _ = self._debug and self._logger.debug("%s: entering context...", self)
744
746
  self._is_entered.set()
745
- self._entries += 1
746
- self._task = create_task(self.run_looper())
747
+ async with self._lock:
748
+ self._entries += 1
749
+ self._task = create_task(self.run_looper())
747
750
  for looper in self._yield_sub_loopers():
748
751
  _ = self._debug and self._logger.debug(
749
752
  "%s: adding sub-looper %s", self, looper
@@ -752,7 +755,8 @@ class Looper(Generic[_T]):
752
755
  self._logger.warning(
753
756
  "%s: changing sub-looper %s to auto-start...", self, looper
754
757
  )
755
- looper.auto_start = True
758
+ async with self._lock:
759
+ looper.auto_start = True
756
760
  _ = await self._stack.enter_async_context(looper)
757
761
  if self.auto_start:
758
762
  _ = self._debug and self._logger.debug("%s: auto-starting...", self)
@@ -797,9 +801,6 @@ class Looper(Generic[_T]):
797
801
  case Task() as task:
798
802
  return task.__await__()
799
803
  case _ as never:
800
- self._logger.warning( # pragma: no cover
801
- "Got %s of type %s", self._task, type(self._task)
802
- )
803
804
  assert_never(never)
804
805
 
805
806
  def __len__(self) -> int:
@@ -812,6 +813,10 @@ class Looper(Generic[_T]):
812
813
  """Check if the queue is empty."""
813
814
  return self._queue.empty()
814
815
 
816
+ def get_all_nowait(self, *, reverse: bool = False) -> Sequence[_T]:
817
+ """Remove and return all items from the queue without blocking."""
818
+ return self._queue.get_all_nowait(reverse=reverse)
819
+
815
820
  def get_left_nowait(self) -> _T:
816
821
  """Remove and return an item from the start of the queue without blocking."""
817
822
  return self._queue.get_left_nowait()
@@ -820,7 +825,7 @@ class Looper(Generic[_T]):
820
825
  """Remove and return an item from the end of the queue without blocking."""
821
826
  return self._queue.get_right_nowait()
822
827
 
823
- async def initialize(self) -> Exception | None:
828
+ async def initialize(self, *, sleep_if_failure: bool) -> Exception | None:
824
829
  """Initialize the looper."""
825
830
  match self._is_initializing.is_set():
826
831
  case True:
@@ -830,23 +835,38 @@ class Looper(Generic[_T]):
830
835
  _ = self._debug and self._logger.debug("%s: initializing...", self)
831
836
  self._is_initializing.set()
832
837
  self._is_initialized.clear()
833
- self._initialization_attempts += 1
838
+ async with self._lock:
839
+ self._initialization_attempts += 1
834
840
  try:
835
841
  await self._initialize_core()
836
842
  except Exception as error: # noqa: BLE001
837
- _ = self._logger.warning(
838
- "%s: encountered %s whilst initializing",
839
- self,
840
- repr_error(error),
841
- )
842
- self._initialization_failures += 1
843
+ async with self._lock:
844
+ self._initialization_failures += 1
843
845
  ret = error
846
+ match sleep_if_failure:
847
+ case True:
848
+ _ = self._logger.warning(
849
+ "%s: encountered %s whilst initializing; sleeping for %s...",
850
+ self,
851
+ repr_error(error),
852
+ self.backoff,
853
+ )
854
+ await sleep(self._backoff)
855
+ case False:
856
+ _ = self._logger.warning(
857
+ "%s: encountered %s whilst initializing",
858
+ self,
859
+ repr_error(error),
860
+ )
861
+ case _ as never:
862
+ assert_never(never)
844
863
  else:
845
864
  _ = self._debug and self._logger.debug(
846
865
  "%s: finished initializing", self
847
866
  )
848
867
  self._is_initialized.set()
849
- self._initialization_successes += 1
868
+ async with self._lock:
869
+ self._initialization_successes += 1
850
870
  ret = None
851
871
  finally:
852
872
  self._is_initializing.clear()
@@ -931,39 +951,75 @@ class Looper(Generic[_T]):
931
951
  case _ as never:
932
952
  assert_never(never)
933
953
 
934
- async def restart(self) -> None:
954
+ async def restart(self, *, sleep_if_failure: bool) -> None:
935
955
  """Restart the looper."""
936
956
  _ = self._debug and self._logger.debug("%s: restarting...", self)
937
957
  self._is_pending_restart.clear()
938
- self._restart_attempts += 1
939
- tear_down = await self.tear_down()
940
- initialization = await self.initialize()
941
- match tear_down, initialization:
942
- case None, None:
958
+ async with self._lock:
959
+ self._restart_attempts += 1
960
+ tear_down = await self.tear_down(sleep_if_failure=False)
961
+ initialization = await self.initialize(sleep_if_failure=False)
962
+ match tear_down, initialization, sleep_if_failure:
963
+ case None, None, bool():
943
964
  _ = self._debug and self._logger.debug("%s: finished restarting", self)
944
- self._restart_successes += 1
945
- case Exception(), None:
965
+ async with self._lock:
966
+ self._restart_successes += 1
967
+ case Exception(), None, True:
968
+ async with self._lock:
969
+ self._restart_failures += 1
970
+ _ = self._logger.warning(
971
+ "%s: encountered %s whilst restarting (tear down); sleeping for %s...",
972
+ self,
973
+ repr_error(tear_down),
974
+ self.backoff,
975
+ )
976
+ await sleep(self._backoff)
977
+ case Exception(), None, False:
978
+ async with self._lock:
979
+ self._restart_failures += 1
946
980
  _ = self._logger.warning(
947
- "%s: encountered %s whilst restarting, during tear down",
981
+ "%s: encountered %s whilst restarting (tear down)",
948
982
  self,
949
983
  repr_error(tear_down),
950
984
  )
951
- self._restart_failures += 1
952
- case None, Exception():
985
+ case None, Exception(), True:
986
+ async with self._lock:
987
+ self._restart_failures += 1
953
988
  _ = self._logger.warning(
954
- "%s: encountered %s whilst restarting, during initialization",
989
+ "%s: encountered %s whilst restarting (initialize); sleeping for %s...",
955
990
  self,
956
991
  repr_error(initialization),
992
+ self.backoff,
957
993
  )
958
- self._restart_failures += 1
959
- case Exception(), Exception():
994
+ await sleep(self._backoff)
995
+ case None, Exception(), False:
996
+ async with self._lock:
997
+ self._restart_failures += 1
998
+ _ = self._logger.warning(
999
+ "%s: encountered %s whilst restarting (initialize)",
1000
+ self,
1001
+ repr_error(initialization),
1002
+ )
1003
+ case Exception(), Exception(), True:
1004
+ async with self._lock:
1005
+ self._restart_failures += 1
1006
+ _ = self._logger.warning(
1007
+ "%s: encountered %s (tear down) and then %s (initialization) whilst restarting; sleeping for %s...",
1008
+ self,
1009
+ repr_error(tear_down),
1010
+ repr_error(initialization),
1011
+ self.backoff,
1012
+ )
1013
+ await sleep(self._backoff)
1014
+ case Exception(), Exception(), False:
1015
+ async with self._lock:
1016
+ self._restart_failures += 1
960
1017
  _ = self._logger.warning(
961
1018
  "%s: encountered %s (tear down) and then %s (initialization) whilst restarting",
962
1019
  self,
963
1020
  repr_error(tear_down),
964
1021
  repr_error(initialization),
965
1022
  )
966
- self._restart_failures += 1
967
1023
  case _ as never:
968
1024
  assert_never(never)
969
1025
 
@@ -979,12 +1035,13 @@ class Looper(Generic[_T]):
979
1035
  ):
980
1036
  await self.stop()
981
1037
  elif self._is_pending_restart.is_set():
982
- await self.restart()
1038
+ await self.restart(sleep_if_failure=True)
983
1039
  elif not self._is_initialized.is_set():
984
- _ = await self.initialize()
1040
+ _ = await self.initialize(sleep_if_failure=True)
985
1041
  else:
986
1042
  _ = self._debug and self._logger.debug("%s: running core...", self)
987
- self._core_attempts += 1
1043
+ async with self._lock:
1044
+ self._core_attempts += 1
988
1045
  try:
989
1046
  await self.core()
990
1047
  except Exception as error: # noqa: BLE001
@@ -993,11 +1050,13 @@ class Looper(Generic[_T]):
993
1050
  self,
994
1051
  repr_error(error),
995
1052
  )
996
- self._core_failures += 1
1053
+ async with self._lock:
1054
+ self._core_failures += 1
997
1055
  self.request_restart()
998
1056
  await sleep(self._backoff)
999
1057
  else:
1000
- self._core_successes += 1
1058
+ async with self._lock:
1059
+ self._core_successes += 1
1001
1060
  await sleep(self._freq)
1002
1061
 
1003
1062
  @property
@@ -1029,12 +1088,13 @@ class Looper(Generic[_T]):
1029
1088
  _ = self._debug and self._logger.debug("%s: stopping...", self)
1030
1089
  self._is_pending_stop.clear()
1031
1090
  self._is_stopped.set()
1032
- self._stops += 1
1091
+ async with self._lock:
1092
+ self._stops += 1
1033
1093
  _ = self._debug and self._logger.debug("%s: stopped", self)
1034
1094
  case _ as never:
1035
1095
  assert_never(never)
1036
1096
 
1037
- async def tear_down(self) -> Exception | None:
1097
+ async def tear_down(self, *, sleep_if_failure: bool) -> Exception | None:
1038
1098
  """Tear down the looper."""
1039
1099
  match self._is_tearing_down.is_set():
1040
1100
  case True:
@@ -1043,22 +1103,37 @@ class Looper(Generic[_T]):
1043
1103
  case False:
1044
1104
  _ = self._debug and self._logger.debug("%s: tearing down...", self)
1045
1105
  self._is_tearing_down.set()
1046
- self._tear_down_attempts += 1
1106
+ async with self._lock:
1107
+ self._tear_down_attempts += 1
1047
1108
  try:
1048
1109
  await self._tear_down_core()
1049
1110
  except Exception as error: # noqa: BLE001
1050
- _ = self._logger.warning(
1051
- "%s: encountered %s whilst tearing down",
1052
- self,
1053
- repr_error(error),
1054
- )
1055
- self._tear_down_failures += 1
1111
+ async with self._lock:
1112
+ self._tear_down_failures += 1
1056
1113
  ret = error
1114
+ match sleep_if_failure:
1115
+ case True:
1116
+ _ = self._logger.warning(
1117
+ "%s: encountered %s whilst tearing down; sleeping for %s...",
1118
+ self,
1119
+ repr_error(error),
1120
+ self.backoff,
1121
+ )
1122
+ await sleep(self._backoff)
1123
+ case False:
1124
+ _ = self._logger.warning(
1125
+ "%s: encountered %s whilst tearing down",
1126
+ self,
1127
+ repr_error(error),
1128
+ )
1129
+ case _ as never:
1130
+ assert_never(never)
1057
1131
  else:
1058
1132
  _ = self._debug and self._logger.debug(
1059
1133
  "%s: finished tearing down", self
1060
1134
  )
1061
- self._tear_down_successes += 1
1135
+ async with self._lock:
1136
+ self._tear_down_successes += 1
1062
1137
  ret = None
1063
1138
  finally:
1064
1139
  self._is_tearing_down.clear()