dycw-utilities 0.131.16__py3-none-any.whl → 0.131.17__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.131.16
3
+ Version: 0.131.17
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,7 +1,7 @@
1
- utilities/__init__.py,sha256=I-o_EPHIejt3ggoklYx3Vx_Wi2XLiPZ3YlzjyX4JBKI,61
1
+ utilities/__init__.py,sha256=VaWub7ik43R7i6IUMJibr6EG62sAZ7zSqzl9IaPeNEs,61
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
- utilities/asyncio.py,sha256=yfKvAIDCRrWdyQMVZMo4DJQx4nVrXoAcqwhNuF95Ryo,38186
4
+ utilities/asyncio.py,sha256=mHnlSA4KPeDaBRts8Rn4sNA_4urodj7gQzs-_8Z-F7A,37587
5
5
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
6
6
  utilities/atools.py,sha256=-bFGIrwYMFR7xl39j02DZMsO_u5x5_Ph7bRlBUFVYyw,1048
7
7
  utilities/cachetools.py,sha256=uBtEv4hD-TuCPX_cQy1lOpLF-QqfwnYGSf0o4Soqydc,2826
@@ -50,9 +50,9 @@ utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
50
50
  utilities/platform.py,sha256=48IOKx1IC6ZJXWG-b56ZQptITcNFhWRjELW72o2dGTA,2398
51
51
  utilities/polars.py,sha256=QlmUpYTqHNkcLnWOQh1TW22W2QyLzvifCvBcbsqhpdE,63272
52
52
  utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
53
- utilities/pottery.py,sha256=-YikdIQFqhFXifyMq5R5z6V6N8y639gAnVVXR4jXxdc,3568
53
+ utilities/pottery.py,sha256=2w3YuoH1KmLaCVqkwSghHTOT8S4xiUskwRHSRqrUEQY,3430
54
54
  utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
55
- utilities/psutil.py,sha256=RtbLKOoIJhqrJmEoHDBVeSD-KPzshtS0FtRXBP9_w2s,3751
55
+ utilities/psutil.py,sha256=ZkwBGfTqAv8hxCyJVVJFctIVQvxhYuB28re3mIPBodc,3761
56
56
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  utilities/pydantic.py,sha256=aP6OKowg2Md4rgQuQ5qTSF4bTbBuq7WtRb7zS3JSRGY,1841
58
58
  utilities/pyinstrument.py,sha256=3MPNDbZW_1Aj1aA1_f-yqPStgmjIFxPwIafYq2dsaTs,853
@@ -62,20 +62,19 @@ utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA
62
62
  utilities/python_dotenv.py,sha256=edXsvHZhZnYeqfMfrsRRpj7_9eJI6uizh3xLx8Q9B3w,3228
63
63
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
64
64
  utilities/re.py,sha256=6qxeV0rQZaBDKWcB7apSBmxtg_XzoGY-EdegTkMn-ZY,4578
65
- utilities/redis.py,sha256=IceT5EjgrebVkGL8X3M35xlqjI2c7zFbyV1P4dExN4M,36037
65
+ utilities/redis.py,sha256=oA70YoFnSaSqTBzMHrbHkkGIBWU69-97MQEwH_0iX3g,35762
66
66
  utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
67
67
  utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
68
68
  utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
69
69
  utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
70
- utilities/slack_sdk.py,sha256=ltmzv68aa73CJGqTDvt8L9vDm22YU9iOCo3NCiNd3vA,4347
70
+ utilities/slack_sdk.py,sha256=ZpPzV_oHpSJbontGo2_YVVjKH08PjLjsx5Me-nLy918,4246
71
71
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
72
- utilities/sqlalchemy.py,sha256=IuQ7CIVNl29TG6i81K6fam8NmTmPjtA6OiIN4nIM9W8,37935
73
- utilities/sqlalchemy_polars.py,sha256=hApbjQUY-XgKfAXcun8gDP2lGh5LxrudnCpbG_hrYa0,14968
72
+ utilities/sqlalchemy.py,sha256=sTlxK-Is-XCj7srTRh3Nz68axzIdDYZWLQdTeXnOdkQ,38015
73
+ utilities/sqlalchemy_polars.py,sha256=bDiKqHxOWu0Dj4ZDuGcVgR7ulm7sB90iVNINAKaeaKc,14290
74
74
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
75
75
  utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
76
76
  utilities/string.py,sha256=XmU-s04qIV_tODnKl2pQiwmHaxzgOqRKU-RyzdrfvSE,620
77
77
  utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
78
- utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
79
78
  utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
80
79
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
81
80
  utilities/timer.py,sha256=VeSl3ot8-f4D1d3HjjSsgKvjxHJGXd_sW4KcTExOR64,2475
@@ -92,7 +91,7 @@ utilities/whenever.py,sha256=oO0sgIIv4tvhIYmlZ4RFFfY6P54CPeyJCY9A4XHuyqo,11916
92
91
  utilities/whenever2.py,sha256=Jd_fEavXCWTdMj29L7j-HbeMvrl8e3Ah_nxcLEFAwOU,12026
93
92
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
94
93
  utilities/zoneinfo.py,sha256=gJPr9l7V8s3Y7TXpCGYEM1S81Rplb9e4MoV9Nvy2VU8,1852
95
- dycw_utilities-0.131.16.dist-info/METADATA,sha256=X0Yt3BUVE7Y7W884VYvx_9CVhE4c9ENSxbhD4ghFoKU,1585
96
- dycw_utilities-0.131.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
- dycw_utilities-0.131.16.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
98
- dycw_utilities-0.131.16.dist-info/RECORD,,
94
+ dycw_utilities-0.131.17.dist-info/METADATA,sha256=H8TXOjXCrBtmH2V30ZUSMr1mz56J-27FJYHgaoof7Oc,1585
95
+ dycw_utilities-0.131.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.131.17.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.131.17.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.131.16"
3
+ __version__ = "0.131.17"
utilities/asyncio.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  from asyncio import (
4
5
  Event,
5
6
  Lock,
@@ -14,7 +15,6 @@ from asyncio import (
14
15
  create_subprocess_shell,
15
16
  create_task,
16
17
  sleep,
17
- timeout,
18
18
  )
19
19
  from collections.abc import Callable, Iterable, Iterator
20
20
  from contextlib import (
@@ -43,28 +43,22 @@ from typing import (
43
43
  )
44
44
 
45
45
  from typing_extensions import deprecated
46
- from whenever import TimeDelta
47
46
 
48
47
  from utilities.dataclasses import replace_non_sentinel
49
- from utilities.datetime import (
50
- SECOND,
51
- datetime_duration_to_float,
52
- get_now,
53
- round_datetime,
54
- )
55
48
  from utilities.errors import repr_error
56
49
  from utilities.functions import ensure_int, ensure_not_none
57
50
  from utilities.random import SYSTEM_RANDOM
58
51
  from utilities.sentinel import Sentinel, sentinel
59
52
  from utilities.types import (
53
+ DateTimeRoundUnit,
60
54
  MaybeCallableEvent,
61
55
  MaybeType,
62
56
  THashable,
63
57
  TSupportsRichComparison,
64
58
  )
59
+ from utilities.whenever2 import SECOND, get_now
65
60
 
66
61
  if TYPE_CHECKING:
67
- import datetime as dt
68
62
  from asyncio import _CoroutineLike
69
63
  from asyncio.subprocess import Process
70
64
  from collections import deque
@@ -73,7 +67,7 @@ if TYPE_CHECKING:
73
67
  from random import Random
74
68
  from types import TracebackType
75
69
 
76
- from utilities.types import Duration
70
+ from whenever import TimeDelta, ZonedDateTime
77
71
 
78
72
 
79
73
  _T = TypeVar("_T")
@@ -244,7 +238,7 @@ class EnhancedTaskGroup(TaskGroup):
244
238
  """Task group with enhanced features."""
245
239
 
246
240
  _semaphore: Semaphore | None
247
- _timeout: Duration | None
241
+ _timeout: TimeDelta | None
248
242
  _error: MaybeType[BaseException]
249
243
  _stack: AsyncExitStack
250
244
  _timeout_cm: _AsyncGeneratorContextManager[None] | None
@@ -254,7 +248,7 @@ class EnhancedTaskGroup(TaskGroup):
254
248
  self,
255
249
  *,
256
250
  max_tasks: int | None = None,
257
- timeout: Duration | None = None,
251
+ timeout: TimeDelta | None = None,
258
252
  error: MaybeType[BaseException] = TimeoutError,
259
253
  ) -> None:
260
254
  super().__init__()
@@ -306,7 +300,7 @@ class EnhancedTaskGroup(TaskGroup):
306
300
  return await coroutine
307
301
 
308
302
  async def _wrap_with_timeout(self, coroutine: _CoroutineLike[_T], /) -> _T:
309
- async with timeout_dur(duration=self._timeout, error=self._error):
303
+ async with timeout_td(self._timeout, error=self._error):
310
304
  return await coroutine
311
305
 
312
306
 
@@ -331,15 +325,13 @@ class Looper(Generic[_T]):
331
325
  """A looper of a core coroutine, handling errors."""
332
326
 
333
327
  auto_start: bool = field(default=False, repr=False)
334
- freq: Duration = field(default=SECOND, repr=False)
335
- backoff: Duration = field(default=10 * SECOND, repr=False)
328
+ freq: TimeDelta = field(default=SECOND, repr=False)
329
+ backoff: TimeDelta = field(default=10 * SECOND, repr=False)
336
330
  empty_upon_exit: bool = field(default=False, repr=False)
337
331
  logger: str | None = field(default=None, repr=False)
338
- timeout: Duration | None = field(default=None, repr=False)
332
+ timeout: TimeDelta | None = field(default=None, repr=False)
339
333
  # settings
340
- _backoff: float = field(init=False, repr=False)
341
334
  _debug: bool = field(default=False, repr=False)
342
- _freq: float = field(init=False, repr=False)
343
335
  # counts
344
336
  _entries: int = field(default=0, init=False, repr=False)
345
337
  _core_attempts: int = field(default=0, init=False, repr=False)
@@ -379,8 +371,6 @@ class Looper(Generic[_T]):
379
371
  _task: Task[None] | None = field(default=None, init=False, repr=False, hash=False)
380
372
 
381
373
  def __post_init__(self) -> None:
382
- self._backoff = datetime_duration_to_float(self.backoff)
383
- self._freq = datetime_duration_to_float(self.freq)
384
374
  self._logger = getLogger(name=self.logger)
385
375
  self._logger.setLevel(DEBUG)
386
376
 
@@ -448,7 +438,7 @@ class Looper(Generic[_T]):
448
438
 
449
439
  async def _apply_back_off(self) -> None:
450
440
  """Apply a back off period."""
451
- await sleep(self._backoff)
441
+ await sleep_td(self.backoff)
452
442
  self._is_pending_back_off.clear()
453
443
 
454
444
  async def core(self) -> None:
@@ -541,10 +531,10 @@ class Looper(Generic[_T]):
541
531
  *,
542
532
  auto_start: bool | Sentinel = sentinel,
543
533
  empty_upon_exit: bool | Sentinel = sentinel,
544
- freq: Duration | Sentinel = sentinel,
545
- backoff: Duration | Sentinel = sentinel,
534
+ freq: TimeDelta | Sentinel = sentinel,
535
+ backoff: TimeDelta | Sentinel = sentinel,
546
536
  logger: str | None | Sentinel = sentinel,
547
- timeout: Duration | None | Sentinel = sentinel,
537
+ timeout: TimeDelta | None | Sentinel = sentinel,
548
538
  _debug: bool | Sentinel = sentinel,
549
539
  **kwargs: Any,
550
540
  ) -> Self:
@@ -670,7 +660,7 @@ class Looper(Generic[_T]):
670
660
  async def run_looper(self) -> None:
671
661
  """Run the looper."""
672
662
  try:
673
- async with timeout_dur(duration=self.timeout):
663
+ async with timeout_td(self.timeout):
674
664
  while True:
675
665
  if self._is_stopped.is_set():
676
666
  _ = self._debug and self._logger.debug("%s: stopped", self)
@@ -705,7 +695,7 @@ class Looper(Generic[_T]):
705
695
  else:
706
696
  async with self._lock:
707
697
  self._core_successes += 1
708
- await sleep(self._freq)
698
+ await sleep_td(self.freq)
709
699
  except RuntimeError as error: # pragma: no cover
710
700
  if error.args[0] == "generator didn't stop after athrow()":
711
701
  return
@@ -718,7 +708,7 @@ class Looper(Generic[_T]):
718
708
  while not self.empty():
719
709
  await self.core()
720
710
  if not self.empty():
721
- await sleep(self._freq)
711
+ await sleep_td(self.freq)
722
712
 
723
713
  @property
724
714
  def stats(self) -> _LooperStats:
@@ -962,47 +952,41 @@ def put_items_nowait(items: Iterable[_T], queue: Queue[_T], /) -> None:
962
952
  ##
963
953
 
964
954
 
965
- async def sleep_dur(*, duration: Duration | TimeDelta | None = None) -> None:
966
- """Sleep which accepts durations."""
967
- if duration is None:
955
+ async def sleep_max(
956
+ sleep: TimeDelta | None = None, /, *, random: Random = SYSTEM_RANDOM
957
+ ) -> None:
958
+ """Sleep which accepts deltas."""
959
+ if sleep is None:
968
960
  return
969
- if isinstance(duration, TimeDelta):
970
- await sleep(duration.in_seconds())
971
- else:
972
- await sleep(datetime_duration_to_float(duration))
961
+ await asyncio.sleep(random.uniform(0.0, sleep.in_seconds()))
973
962
 
974
963
 
975
964
  ##
976
965
 
977
966
 
978
- async def sleep_max_dur(
979
- *, duration: Duration | None = None, random: Random = SYSTEM_RANDOM
967
+ async def sleep_rounded(
968
+ *, unit: DateTimeRoundUnit = "second", increment: int = 1
980
969
  ) -> None:
981
- """Sleep which accepts max durations."""
982
- if duration is None:
983
- return
984
- await sleep(random.uniform(0.0, datetime_duration_to_float(duration)))
970
+ """Sleep until a rounded time."""
971
+ await sleep_until(get_now().round(unit, increment=increment, mode="ceil"))
985
972
 
986
973
 
987
974
  ##
988
975
 
989
976
 
990
- async def sleep_until(datetime: dt.datetime, /) -> None:
991
- """Sleep until a given time."""
992
- await sleep_dur(duration=datetime - get_now())
977
+ async def sleep_td(delta: TimeDelta | None = None, /) -> None:
978
+ """Sleep which accepts deltas."""
979
+ if delta is None:
980
+ return
981
+ await sleep(delta.in_seconds())
993
982
 
994
983
 
995
984
  ##
996
985
 
997
986
 
998
- async def sleep_until_rounded(
999
- duration: Duration, /, *, rel_tol: float | None = None, abs_tol: float | None = None
1000
- ) -> None:
1001
- """Sleep until a rounded time; accepts durations."""
1002
- datetime = round_datetime(
1003
- get_now(), duration, mode="ceil", rel_tol=rel_tol, abs_tol=abs_tol
1004
- )
1005
- await sleep_until(datetime)
987
+ async def sleep_until(datetime: ZonedDateTime, /) -> None:
988
+ """Sleep until a given time."""
989
+ await sleep_td(datetime - get_now())
1006
990
 
1007
991
 
1008
992
  ##
@@ -1059,13 +1043,16 @@ async def _stream_one(
1059
1043
 
1060
1044
 
1061
1045
  @asynccontextmanager
1062
- async def timeout_dur(
1063
- *, duration: Duration | None = None, error: MaybeType[BaseException] = TimeoutError
1046
+ async def timeout_td(
1047
+ timeout: TimeDelta | None = None,
1048
+ /,
1049
+ *,
1050
+ error: MaybeType[BaseException] = TimeoutError,
1064
1051
  ) -> AsyncIterator[None]:
1065
- """Timeout context manager which accepts durations."""
1066
- delay = None if duration is None else datetime_duration_to_float(duration)
1052
+ """Timeout context manager which accepts deltas."""
1053
+ timeout_use = None if timeout is None else timeout.in_seconds()
1067
1054
  try:
1068
- async with timeout(delay):
1055
+ async with asyncio.timeout(timeout_use):
1069
1056
  yield
1070
1057
  except TimeoutError:
1071
1058
  raise error from None
@@ -1084,10 +1071,10 @@ __all__ = [
1084
1071
  "get_items_nowait",
1085
1072
  "put_items",
1086
1073
  "put_items_nowait",
1087
- "sleep_dur",
1088
- "sleep_max_dur",
1074
+ "sleep_max",
1075
+ "sleep_rounded",
1076
+ "sleep_td",
1089
1077
  "sleep_until",
1090
- "sleep_until_rounded",
1091
1078
  "stream_command",
1092
- "timeout_dur",
1079
+ "timeout_td",
1093
1080
  ]
utilities/pottery.py CHANGED
@@ -8,14 +8,16 @@ from pottery import AIORedlock
8
8
  from pottery.exceptions import ReleaseUnlockedLock
9
9
  from redis.asyncio import Redis
10
10
 
11
- from utilities.asyncio import sleep_dur, timeout_dur
12
- from utilities.datetime import MILLISECOND, SECOND, datetime_duration_to_float
11
+ from utilities.asyncio import sleep_td, timeout_td
13
12
  from utilities.iterables import always_iterable
13
+ from utilities.whenever2 import MILLISECOND, SECOND
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from collections.abc import AsyncIterator, Iterable
17
17
 
18
- from utilities.types import Duration, MaybeIterable
18
+ from whenever import TimeDelta
19
+
20
+ from utilities.types import MaybeIterable
19
21
 
20
22
 
21
23
  @asynccontextmanager
@@ -25,10 +27,10 @@ async def yield_access(
25
27
  /,
26
28
  *,
27
29
  num: int = 1,
28
- timeout_acquire: Duration | None = None,
29
- timeout_release: Duration = 10 * SECOND,
30
- sleep: Duration = MILLISECOND,
31
- throttle: Duration | None = None,
30
+ timeout_acquire: TimeDelta | None = None,
31
+ timeout_release: TimeDelta = 10 * SECOND,
32
+ sleep: TimeDelta = MILLISECOND,
33
+ throttle: TimeDelta | None = None,
32
34
  ) -> AsyncIterator[None]:
33
35
  """Acquire access to a locked resource, amongst 1 of multiple connections."""
34
36
  if num <= 0:
@@ -36,14 +38,11 @@ async def yield_access(
36
38
  masters = ( # skipif-ci-and-not-linux
37
39
  {redis} if isinstance(redis, Redis) else set(always_iterable(redis))
38
40
  )
39
- auto_release_time = datetime_duration_to_float( # skipif-ci-and-not-linux
40
- timeout_release
41
- )
42
41
  locks = [ # skipif-ci-and-not-linux
43
42
  AIORedlock(
44
43
  key=f"{key}_{i}_of_{num}",
45
44
  masters=masters,
46
- auto_release_time=auto_release_time,
45
+ auto_release_time=timeout_release.in_seconds(),
47
46
  )
48
47
  for i in range(1, num + 1)
49
48
  ]
@@ -54,7 +53,7 @@ async def yield_access(
54
53
  )
55
54
  yield
56
55
  finally: # skipif-ci-and-not-linux
57
- await sleep_dur(duration=throttle)
56
+ await sleep_td(throttle)
58
57
  if lock is not None:
59
58
  with suppress(ReleaseUnlockedLock):
60
59
  await lock.release()
@@ -66,20 +65,18 @@ async def _get_first_available_lock(
66
65
  /,
67
66
  *,
68
67
  num: int = 1,
69
- timeout: Duration | None = None,
70
- sleep: Duration | None = None,
68
+ timeout: TimeDelta | None = None,
69
+ sleep: TimeDelta | None = None,
71
70
  ) -> AIORedlock:
72
71
  locks = list(locks) # skipif-ci-and-not-linux
73
72
  error = _YieldAccessUnableToAcquireLockError( # skipif-ci-and-not-linux
74
73
  key=key, num=num, timeout=timeout
75
74
  )
76
- async with timeout_dur( # skipif-ci-and-not-linux
77
- duration=timeout, error=error
78
- ):
75
+ async with timeout_td(timeout, error=error): # skipif-ci-and-not-linux
79
76
  while True:
80
77
  if (result := await _get_first_available_lock_if_any(locks)) is not None:
81
78
  return result
82
- await sleep_dur(duration=sleep)
79
+ await sleep_td(sleep)
83
80
 
84
81
 
85
82
  async def _get_first_available_lock_if_any(
@@ -106,7 +103,7 @@ class _YieldAccessNumLocksError(YieldAccessError):
106
103
 
107
104
  @dataclass(kw_only=True, slots=True)
108
105
  class _YieldAccessUnableToAcquireLockError(YieldAccessError):
109
- timeout: Duration | None
106
+ timeout: TimeDelta | None
110
107
 
111
108
  @override
112
109
  def __str__(self) -> str:
utilities/psutil.py CHANGED
@@ -11,13 +11,14 @@ from psutil import swap_memory, virtual_memory
11
11
 
12
12
  from utilities.asyncio import Looper
13
13
  from utilities.contextlib import suppress_super_object_attribute_error
14
- from utilities.datetime import SECOND, get_now
14
+ from utilities.whenever2 import SECOND, get_now
15
15
 
16
16
  if TYPE_CHECKING:
17
- import datetime as dt
18
17
  from logging import Logger
19
18
 
20
- from utilities.types import Duration, PathLike
19
+ from whenever import TimeDelta, ZonedDateTime
20
+
21
+ from utilities.types import PathLike
21
22
 
22
23
 
23
24
  @dataclass(kw_only=True)
@@ -25,8 +26,8 @@ class MemoryMonitorService(Looper[None]):
25
26
  """Service to monitor memory usage."""
26
27
 
27
28
  # base
28
- freq: Duration = field(default=10 * SECOND, repr=False)
29
- backoff: Duration = field(default=10 * SECOND, repr=False)
29
+ freq: TimeDelta = field(default=10 * SECOND, repr=False)
30
+ backoff: TimeDelta = field(default=10 * SECOND, repr=False)
30
31
  # self
31
32
  console: str | None = field(default=None, repr=False)
32
33
  path: PathLike = "memory.txt"
@@ -46,7 +47,7 @@ class MemoryMonitorService(Looper[None]):
46
47
  await super().core()
47
48
  memory = MemoryUsage.new()
48
49
  mapping = {
49
- "datetime": memory.datetime.strftime("%Y-%m-%d %H:%M:%S"),
50
+ "datetime": memory.datetime.format_common_iso(),
50
51
  "virtual used (mb)": memory.virtual_used_mb,
51
52
  "virtual total (mb)": memory.virtual_total_mb,
52
53
  "virtual (%)": memory.virtual_pct,
@@ -68,7 +69,7 @@ class MemoryMonitorService(Looper[None]):
68
69
  class MemoryUsage:
69
70
  """A memory usage."""
70
71
 
71
- datetime: dt.datetime = field(default_factory=get_now)
72
+ datetime: ZonedDateTime = field(default_factory=get_now)
72
73
  virtual_used: int = field(repr=False)
73
74
  virtual_used_mb: int = field(init=False)
74
75
  virtual_total: int = field(repr=False)