dycw-utilities 0.125.23__py3-none-any.whl → 0.125.25__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.
- {dycw_utilities-0.125.23.dist-info → dycw_utilities-0.125.25.dist-info}/METADATA +1 -1
- {dycw_utilities-0.125.23.dist-info → dycw_utilities-0.125.25.dist-info}/RECORD +7 -7
- utilities/__init__.py +1 -1
- utilities/asyncio.py +14 -0
- utilities/slack_sdk.py +93 -3
- {dycw_utilities-0.125.23.dist-info → dycw_utilities-0.125.25.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.125.23.dist-info → dycw_utilities-0.125.25.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=3MYZAqKLfaJb4ielcVRGJ7gJj-yObA5kgfPLoc-wN1Q,61
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
|
-
utilities/asyncio.py,sha256=
|
3
|
+
utilities/asyncio.py,sha256=K5Kj7rsM0nA17-b7d7mrNgPR1U_NbkfQmTruq5LBLRA,51778
|
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
|
@@ -66,7 +66,7 @@ utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
|
66
66
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
67
67
|
utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
|
68
68
|
utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
|
69
|
-
utilities/slack_sdk.py,sha256=
|
69
|
+
utilities/slack_sdk.py,sha256=h2DiVkcFyYcT5zzZOAo6CSith5BBlHUbXeOJSL1neK8,5948
|
70
70
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
71
71
|
utilities/sqlalchemy.py,sha256=p8vsHaNRoeq5zJouIKyp9piFM26wtm5yR4DkzCMFDSw,35471
|
72
72
|
utilities/sqlalchemy_polars.py,sha256=s7hQNep2O5DTgIRXyN_JRQma7a4DAtNd25tshaZW8iw,15490
|
@@ -90,7 +90,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
90
90
|
utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
91
91
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
92
92
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
93
|
-
dycw_utilities-0.125.
|
94
|
-
dycw_utilities-0.125.
|
95
|
-
dycw_utilities-0.125.
|
96
|
-
dycw_utilities-0.125.
|
93
|
+
dycw_utilities-0.125.25.dist-info/METADATA,sha256=TYTHhscXtNic_ikhuiW7WQN30GUx2y-ay4RXYCt3qFI,12852
|
94
|
+
dycw_utilities-0.125.25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
95
|
+
dycw_utilities-0.125.25.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
96
|
+
dycw_utilities-0.125.25.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -688,6 +688,7 @@ class Looper(Generic[_T]):
|
|
688
688
|
auto_start: bool = field(default=False, repr=False)
|
689
689
|
freq: Duration = field(default=SECOND, repr=False)
|
690
690
|
backoff: Duration = field(default=10 * SECOND, repr=False)
|
691
|
+
empty_upon_exit: bool = field(default=False, repr=False)
|
691
692
|
logger: str | None = field(default=None, repr=False)
|
692
693
|
timeout: Duration | None = field(default=None, repr=False)
|
693
694
|
timeout_error: type[Exception] = field(default=LooperTimeoutError, repr=False)
|
@@ -791,6 +792,8 @@ class Looper(Generic[_T]):
|
|
791
792
|
)
|
792
793
|
_ = await self._stack.__aexit__(exc_type, exc_value, traceback)
|
793
794
|
await self.stop()
|
795
|
+
if self.empty_upon_exit:
|
796
|
+
await self.run_until_empty()
|
794
797
|
case False:
|
795
798
|
_ = self._debug and self._logger.debug("%s: already exited", self)
|
796
799
|
case _ as never:
|
@@ -891,23 +894,27 @@ class Looper(Generic[_T]):
|
|
891
894
|
self,
|
892
895
|
*,
|
893
896
|
auto_start: bool | Sentinel = sentinel,
|
897
|
+
empty_upon_exit: bool | Sentinel = sentinel,
|
894
898
|
freq: Duration | Sentinel = sentinel,
|
895
899
|
backoff: Duration | Sentinel = sentinel,
|
896
900
|
logger: str | None | Sentinel = sentinel,
|
897
901
|
timeout: Duration | None | Sentinel = sentinel,
|
898
902
|
timeout_error: type[Exception] | Sentinel = sentinel,
|
899
903
|
_debug: bool | Sentinel = sentinel,
|
904
|
+
**kwargs: Any,
|
900
905
|
) -> Self:
|
901
906
|
"""Replace elements of the looper."""
|
902
907
|
return replace_non_sentinel(
|
903
908
|
self,
|
904
909
|
auto_start=auto_start,
|
910
|
+
empty_upon_exit=empty_upon_exit,
|
905
911
|
freq=freq,
|
906
912
|
backoff=backoff,
|
907
913
|
logger=logger,
|
908
914
|
timeout=timeout,
|
909
915
|
timeout_error=timeout_error,
|
910
916
|
_debug=_debug,
|
917
|
+
**kwargs,
|
911
918
|
)
|
912
919
|
|
913
920
|
def request_restart(self) -> None:
|
@@ -1061,6 +1068,13 @@ class Looper(Generic[_T]):
|
|
1061
1068
|
self._core_successes += 1
|
1062
1069
|
await sleep(self._freq)
|
1063
1070
|
|
1071
|
+
async def run_until_empty(self) -> None:
|
1072
|
+
"""Run until the queue is empty."""
|
1073
|
+
while not self.empty():
|
1074
|
+
await self.core()
|
1075
|
+
if not self.empty():
|
1076
|
+
await sleep(self._freq)
|
1077
|
+
|
1064
1078
|
@property
|
1065
1079
|
def stats(self) -> _LooperStats:
|
1066
1080
|
"""Return the statistics."""
|
utilities/slack_sdk.py
CHANGED
@@ -3,14 +3,20 @@ from __future__ import annotations
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from http import HTTPStatus
|
5
5
|
from logging import NOTSET, Handler, LogRecord
|
6
|
-
from typing import TYPE_CHECKING, override
|
6
|
+
from typing import TYPE_CHECKING, Any, Self, override
|
7
7
|
|
8
8
|
from slack_sdk.webhook.async_client import AsyncWebhookClient
|
9
9
|
|
10
|
-
from utilities.asyncio import
|
10
|
+
from utilities.asyncio import (
|
11
|
+
InfiniteQueueLooper,
|
12
|
+
Looper,
|
13
|
+
LooperTimeoutError,
|
14
|
+
timeout_dur,
|
15
|
+
)
|
11
16
|
from utilities.datetime import MINUTE, SECOND, datetime_duration_to_float
|
12
17
|
from utilities.functools import cache
|
13
18
|
from utilities.math import safe_round
|
19
|
+
from utilities.sentinel import Sentinel, sentinel
|
14
20
|
|
15
21
|
if TYPE_CHECKING:
|
16
22
|
from collections.abc import Callable
|
@@ -73,6 +79,90 @@ class SlackHandler(Handler, InfiniteQueueLooper[None, str]):
|
|
73
79
|
await self.sender(self.url, text)
|
74
80
|
|
75
81
|
|
82
|
+
@dataclass(init=False, unsafe_hash=True)
|
83
|
+
class SlackHandlerService(Handler, Looper[str]):
|
84
|
+
"""Service to send messages to Slack."""
|
85
|
+
|
86
|
+
@override
|
87
|
+
def __init__(
|
88
|
+
self,
|
89
|
+
*,
|
90
|
+
url: str,
|
91
|
+
auto_start: bool = False,
|
92
|
+
empty_upon_exit: bool = True,
|
93
|
+
freq: Duration = SECOND,
|
94
|
+
backoff: Duration = SECOND,
|
95
|
+
logger: str | None = None,
|
96
|
+
timeout: Duration | None = None,
|
97
|
+
timeout_error: type[Exception] = LooperTimeoutError,
|
98
|
+
_debug: bool = False,
|
99
|
+
level: int = NOTSET,
|
100
|
+
sender: Callable[[str, str], Coroutine1[None]] = _send_adapter,
|
101
|
+
send_timeout: Duration = SECOND,
|
102
|
+
) -> None:
|
103
|
+
Looper.__init__( # Looper first
|
104
|
+
self,
|
105
|
+
auto_start=auto_start,
|
106
|
+
freq=freq,
|
107
|
+
empty_upon_exit=empty_upon_exit,
|
108
|
+
backoff=backoff,
|
109
|
+
logger=logger,
|
110
|
+
timeout=timeout,
|
111
|
+
timeout_error=timeout_error,
|
112
|
+
_debug=_debug,
|
113
|
+
)
|
114
|
+
Looper.__post_init__(self)
|
115
|
+
Handler.__init__(self, level=level) # Handler next
|
116
|
+
self.url = url
|
117
|
+
self.sender = sender
|
118
|
+
self.send_timeout = send_timeout
|
119
|
+
|
120
|
+
@override
|
121
|
+
def emit(self, record: LogRecord) -> None:
|
122
|
+
fmtted = self.format(record)
|
123
|
+
try:
|
124
|
+
self.put_right_nowait(fmtted)
|
125
|
+
except Exception: # noqa: BLE001 # pragma: no cover
|
126
|
+
self.handleError(record)
|
127
|
+
|
128
|
+
@override
|
129
|
+
async def core(self) -> None:
|
130
|
+
await super().core()
|
131
|
+
if self.empty():
|
132
|
+
return
|
133
|
+
text = "\n".join(self.get_all_nowait())
|
134
|
+
async with timeout_dur(duration=self.send_timeout):
|
135
|
+
await self.sender(self.url, text)
|
136
|
+
|
137
|
+
@override
|
138
|
+
def replace(
|
139
|
+
self,
|
140
|
+
*,
|
141
|
+
auto_start: bool | Sentinel = sentinel,
|
142
|
+
empty_upon_exit: bool | Sentinel = sentinel,
|
143
|
+
freq: Duration | Sentinel = sentinel,
|
144
|
+
backoff: Duration | Sentinel = sentinel,
|
145
|
+
logger: str | None | Sentinel = sentinel,
|
146
|
+
timeout: Duration | None | Sentinel = sentinel,
|
147
|
+
timeout_error: type[Exception] | Sentinel = sentinel,
|
148
|
+
_debug: bool | Sentinel = sentinel,
|
149
|
+
**kwargs: Any,
|
150
|
+
) -> Self:
|
151
|
+
"""Replace elements of the looper."""
|
152
|
+
return super().replace(
|
153
|
+
url=self.url,
|
154
|
+
auto_start=auto_start,
|
155
|
+
empty_upon_exit=empty_upon_exit,
|
156
|
+
freq=freq,
|
157
|
+
backoff=backoff,
|
158
|
+
logger=logger,
|
159
|
+
timeout=timeout,
|
160
|
+
timeout_error=timeout_error,
|
161
|
+
_debug=_debug,
|
162
|
+
**kwargs,
|
163
|
+
)
|
164
|
+
|
165
|
+
|
76
166
|
##
|
77
167
|
|
78
168
|
|
@@ -106,4 +196,4 @@ def _get_client(url: str, /, *, timeout: Duration = _TIMEOUT) -> AsyncWebhookCli
|
|
106
196
|
return AsyncWebhookClient(url, timeout=timeout_use)
|
107
197
|
|
108
198
|
|
109
|
-
__all__ = ["SendToSlackError", "SlackHandler", "send_to_slack"]
|
199
|
+
__all__ = ["SendToSlackError", "SlackHandler", "SlackHandlerService", "send_to_slack"]
|
File without changes
|
File without changes
|