dycw-utilities 0.125.21__py3-none-any.whl → 0.125.23__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.21
3
+ Version: 0.125.23
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=w16go3vHbb763oQxxUHlPrGNReaPnuMsxxD4YRz2JeE,61
1
+ utilities/__init__.py,sha256=wPKNBX6ygqFYs-IuHSilaZXm0vMIdcuqBGP9j1b_5O8,61
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
- utilities/asyncio.py,sha256=pQ5GRcNyBqmDyeAeSarYugrjSNxmX3E0G9pye-kkPt4,51085
3
+ utilities/asyncio.py,sha256=wtMOjHG85exqdwVBNH-DazTprkUCvMFv-JgY-9REhYs,51259
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
@@ -50,6 +50,7 @@ utilities/platform.py,sha256=48IOKx1IC6ZJXWG-b56ZQptITcNFhWRjELW72o2dGTA,2398
50
50
  utilities/polars.py,sha256=QlmUpYTqHNkcLnWOQh1TW22W2QyLzvifCvBcbsqhpdE,63272
51
51
  utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
52
52
  utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
53
+ utilities/psutil.py,sha256=0dXHKW7t9J3uzY3YtHGiRiZVRWr0uozccioZRCVFQfk,3761
53
54
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
55
  utilities/pydantic.py,sha256=f6qtR5mO2YMuyvNmbaEj5YeD9eGA4YYfb7Bjzh9jUs0,1845
55
56
  utilities/pyinstrument.py,sha256=OJFDh4o1CWIa4aYPYURdQjgap_nvP45KUsCEe94rQHY,829
@@ -89,7 +90,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
89
90
  utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
90
91
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
91
92
  utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
92
- dycw_utilities-0.125.21.dist-info/METADATA,sha256=Jq0hDSrXzEM1SXuG6bYbXjJb3dVNQaACnhjTHT_4yhA,12852
93
- dycw_utilities-0.125.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
- dycw_utilities-0.125.21.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
- dycw_utilities-0.125.21.dist-info/RECORD,,
93
+ dycw_utilities-0.125.23.dist-info/METADATA,sha256=sTzpB8MuDKbN3vHMg9otGuFDqzQ6Ko-_iYSb_9W94QY,12852
94
+ dycw_utilities-0.125.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
+ dycw_utilities-0.125.23.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
96
+ dycw_utilities-0.125.23.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.125.21"
3
+ __version__ = "0.125.23"
utilities/asyncio.py CHANGED
@@ -895,6 +895,8 @@ class Looper(Generic[_T]):
895
895
  backoff: Duration | Sentinel = sentinel,
896
896
  logger: str | None | Sentinel = sentinel,
897
897
  timeout: Duration | None | Sentinel = sentinel,
898
+ timeout_error: type[Exception] | Sentinel = sentinel,
899
+ _debug: bool | Sentinel = sentinel,
898
900
  ) -> Self:
899
901
  """Replace elements of the looper."""
900
902
  return replace_non_sentinel(
@@ -904,6 +906,8 @@ class Looper(Generic[_T]):
904
906
  backoff=backoff,
905
907
  logger=logger,
906
908
  timeout=timeout,
909
+ timeout_error=timeout_error,
910
+ _debug=_debug,
907
911
  )
908
912
 
909
913
  def request_restart(self) -> None:
utilities/psutil.py ADDED
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from json import dumps
5
+ from logging import getLogger
6
+ from math import isclose, nan
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Self, override
9
+
10
+ from psutil import swap_memory, virtual_memory
11
+
12
+ from utilities.asyncio import Looper
13
+ from utilities.contextlib import suppress_super_object_attribute_error
14
+ from utilities.datetime import SECOND, get_now
15
+
16
+ if TYPE_CHECKING:
17
+ import datetime as dt
18
+ from logging import Logger
19
+
20
+ from utilities.types import Duration, PathLike
21
+
22
+
23
+ @dataclass(kw_only=True)
24
+ class MemoryMonitorService(Looper[None]):
25
+ """Service to monitor memory usage."""
26
+
27
+ # base
28
+ freq: Duration = field(default=10 * SECOND, repr=False)
29
+ backoff: Duration = field(default=10 * SECOND, repr=False)
30
+ # self
31
+ console: str | None = field(default=None, repr=False)
32
+ path: PathLike = "memory.txt"
33
+ _console: Logger | None = field(init=False, repr=False)
34
+ _max_age: int | None = field(default=None, init=False, repr=False)
35
+ _path: Path = field(init=False, repr=False)
36
+
37
+ @override
38
+ def __post_init__(self) -> None:
39
+ super().__post_init__()
40
+ if self.console is not None:
41
+ self._console = getLogger(self.console)
42
+ self._path = Path(self.path)
43
+
44
+ @override
45
+ async def core(self) -> None:
46
+ await super().core()
47
+ memory = MemoryUsage.new()
48
+ mapping = {
49
+ "datetime": memory.datetime.strftime("%Y-%m-%d %H:%M:%S"),
50
+ "virtual used (mb)": memory.virtual_used_mb,
51
+ "virtual total (mb)": memory.virtual_total_mb,
52
+ "virtual (%)": memory.virtual_pct,
53
+ "swap used (mb)": memory.swap_used_mb,
54
+ "swap total (mb)": memory.swap_total_mb,
55
+ "swap (%)": memory.swap_pct,
56
+ }
57
+ ser = dumps(mapping)
58
+ with self._path.open(mode="a") as fh:
59
+ _ = fh.write(f"{ser}\n")
60
+ if self._console is not None:
61
+ self._console.info("%s", mapping)
62
+
63
+
64
+ ##
65
+
66
+
67
+ @dataclass(kw_only=True)
68
+ class MemoryUsage:
69
+ """A memory usage."""
70
+
71
+ datetime: dt.datetime = field(default_factory=get_now)
72
+ virtual_used: int = field(repr=False)
73
+ virtual_used_mb: int = field(init=False)
74
+ virtual_total: int = field(repr=False)
75
+ virtual_total_mb: int = field(init=False)
76
+ virtual_pct: float = field(init=False)
77
+ swap_used: int = field(repr=False)
78
+ swap_used_mb: int = field(init=False)
79
+ swap_total: int = field(repr=False)
80
+ swap_total_mb: int = field(init=False)
81
+ swap_pct: float = field(init=False)
82
+
83
+ def __post_init__(self) -> None:
84
+ with suppress_super_object_attribute_error():
85
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
86
+ self.virtual_used_mb = self._to_mb(self.virtual_used)
87
+ self.virtual_total_mb = self._to_mb(self.virtual_total)
88
+ self.virtual_pct = (
89
+ nan
90
+ if isclose(self.virtual_total, 0.0)
91
+ else self.virtual_used / self.virtual_total
92
+ )
93
+ self.swap_used_mb = self._to_mb(self.swap_used)
94
+ self.swap_total_mb = self._to_mb(self.swap_total)
95
+ self.swap_pct = (
96
+ nan if isclose(self.swap_total, 0.0) else self.swap_used / self.swap_total
97
+ )
98
+
99
+ @classmethod
100
+ def new(cls) -> Self:
101
+ virtual = virtual_memory()
102
+ virtual_total = virtual.total
103
+ swap = swap_memory()
104
+ return cls(
105
+ virtual_used=virtual_total - virtual.available,
106
+ virtual_total=virtual_total,
107
+ swap_used=swap.used,
108
+ swap_total=swap.total,
109
+ )
110
+
111
+ def _to_mb(self, bytes_: int) -> int:
112
+ return round(bytes_ / (1024**2))
113
+
114
+
115
+ __all__ = ["MemoryMonitorService", "MemoryUsage"]