dycw-utilities 0.114.3__py3-none-any.whl → 0.114.5__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.114.3
3
+ Version: 0.114.5
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=SWDkfIOyhgygmyWsgThn0Boll445H7CW7YAsVtIjpMY,60
1
+ utilities/__init__.py,sha256=Ee7tBy_kvLSTrwmTaOWL0kL-0AqH0DWS7zd_7gJdAj0,60
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
4
- utilities/asyncio.py,sha256=Hysj5gHlizHUhBo3D9Czo5wciJxNoO5HrhXZ4hbC-sk,21478
4
+ utilities/asyncio.py,sha256=d_z20q4pFhjMBruepHKjvbWVFF-89VpP6_o9ZFQIe9k,21508
5
5
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
6
6
  utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
7
7
  utilities/cachetools.py,sha256=C1zqOg7BYz0IfQFK8e3qaDDgEZxDpo47F15RTfJM37Q,2910
@@ -29,7 +29,7 @@ utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
29
29
  utilities/iterables.py,sha256=prKXBdF5QfLTGC-q4567DwO8xzUng_Z-2a4wBkMqyDo,45360
30
30
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
31
31
  utilities/lightweight_charts.py,sha256=vyVOzarYhBIOZj2xDhqdbP85qbSKUjdc6Au91rc1W4M,2814
32
- utilities/logging.py,sha256=55LVJYd2BKzeTTQr4tG7uSNMDVEgUO56wK4s-rldjrM,25326
32
+ utilities/logging.py,sha256=gwo3pusPjnWO1ollrtn1VKYyRAQJTue4SkCbMeNvec4,25715
33
33
  utilities/loguru.py,sha256=MEMQVWrdECxk1e3FxGzmOf21vWT9j8CAir98SEXFKPA,3809
34
34
  utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
35
35
  utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
@@ -64,7 +64,7 @@ utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
64
64
  utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
65
65
  utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
66
66
  utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
67
- utilities/slack_sdk.py,sha256=Gbla983KulSSXnNyzaXgYQLKoq84KvLH8SdhxU-jQ0Q,4126
67
+ utilities/slack_sdk.py,sha256=wPqn9F5AMXgmkp3zgIrBMllLt2SDCCnBNNyi-ag3yzw,5555
68
68
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
69
69
  utilities/sqlalchemy.py,sha256=585hWuuXVTKTnyn0Pfd9JI6jp-hmKW6pLKGYMjXjytM,36959
70
70
  utilities/sqlalchemy_polars.py,sha256=wjJpoUo-yO9E2ujpG_06vV5r2OdvBiQ4yvV6wKCa2Tk,15605
@@ -76,7 +76,7 @@ utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
76
76
  utilities/text.py,sha256=Fo12N4aA7k2rnb4W4vH9iiDh88Q5_nvRssTkfNsvVM8,10965
77
77
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
78
78
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
79
- utilities/traceback.py,sha256=secexUnBsecfWV4ZuqP1W4pGF3prOeO1CRyJK-8zQDU,27402
79
+ utilities/traceback.py,sha256=p9WATV-e4_5AW6SvyRBiU-MY8XnEFcKgrFNUoFzalXI,27521
80
80
  utilities/types.py,sha256=FhE1b8v_s_IlmXucwY7jAMWAq9cfpzyKssQwgwb3jnM,18267
81
81
  utilities/typing.py,sha256=H6ysJkI830aRwLsMKz0SZIw4cpcsm7d6KhQOwr-SDh0,13817
82
82
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
87
87
  utilities/whenever.py,sha256=iLRP_-8CZtBpHKbGZGu-kjSMg1ZubJ-VSmgSy7Eudxw,17787
88
88
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
89
89
  utilities/zoneinfo.py,sha256=-Xm57PMMwDTYpxJdkiJG13wnbwK--I7XItBh5WVhD-o,1874
90
- dycw_utilities-0.114.3.dist-info/METADATA,sha256=vNqkObcQLSh6xKu-ehOh26STTT5syHO9ImD4L7O3dPc,12943
91
- dycw_utilities-0.114.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.114.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.114.3.dist-info/RECORD,,
90
+ dycw_utilities-0.114.5.dist-info/METADATA,sha256=n2XeSZhLo9Msjq6ct0xGXRAtvWEh-YVamV95mGjMh3Y,12943
91
+ dycw_utilities-0.114.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.114.5.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.114.5.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.114.3"
3
+ __version__ = "0.114.5"
utilities/asyncio.py CHANGED
@@ -326,7 +326,7 @@ class ExceptionProcessor(QueueProcessor[Exception | type[Exception]]):
326
326
  ##
327
327
 
328
328
 
329
- @dataclass(kw_only=True)
329
+ @dataclass(kw_only=True, unsafe_hash=True)
330
330
  class InfiniteLooper(ABC, Generic[THashable]):
331
331
  """An infinite loop which can throw exceptions by setting events."""
332
332
 
@@ -334,7 +334,7 @@ class InfiniteLooper(ABC, Generic[THashable]):
334
334
  sleep_restart: Duration = MINUTE
335
335
  logger: str | None = None
336
336
  _events: Mapping[THashable, Event] = field(
337
- default_factory=dict, init=False, repr=False
337
+ default_factory=dict, init=False, repr=False, hash=False
338
338
  )
339
339
 
340
340
  def __post_init__(self) -> None:
utilities/logging.py CHANGED
@@ -587,19 +587,19 @@ def setup_logging(
587
587
 
588
588
  # console
589
589
  if console_level is not None: # skipif-ci-and-windows
590
- console_low_handler = StreamHandler(stream=stdout)
591
- add_filters(console_low_handler, lambda x: x.levelno < ERROR)
592
- add_filters(console_low_handler, *console_filters)
593
- add_filters(console_low_handler, *filters)
594
- console_low_handler.setFormatter(console_formatter)
595
- console_low_handler.setLevel(console_level)
596
- logger_use.addHandler(console_low_handler)
597
-
598
- console_high_handler = StreamHandler(stream=stdout)
599
- add_filters(console_high_handler, *console_filters)
600
- add_filters(console_high_handler, *filters)
590
+ console_low_or_no_exc_handler = StreamHandler(stream=stdout)
591
+ add_filters(console_low_or_no_exc_handler, _console_low_or_no_exc_filter)
592
+ add_filters(console_low_or_no_exc_handler, *console_filters)
593
+ add_filters(console_low_or_no_exc_handler, *filters)
594
+ console_low_or_no_exc_handler.setFormatter(console_formatter)
595
+ console_low_or_no_exc_handler.setLevel(console_level)
596
+ logger_use.addHandler(console_low_or_no_exc_handler)
597
+
598
+ console_high_and_exc_handler = StreamHandler(stream=stdout)
599
+ add_filters(console_high_and_exc_handler, *console_filters)
600
+ add_filters(console_high_and_exc_handler, *filters)
601
601
  _ = RichTracebackFormatter.create_and_set(
602
- console_high_handler,
602
+ console_high_and_exc_handler,
603
603
  version=formatter_version,
604
604
  max_width=formatter_max_width,
605
605
  indent_size=formatter_indent_size,
@@ -610,10 +610,10 @@ def setup_logging(
610
610
  detail=True,
611
611
  post=_ansi_wrap_red,
612
612
  )
613
- console_high_handler.setLevel(
613
+ console_high_and_exc_handler.setLevel(
614
614
  max(get_logging_level_number(console_level), ERROR)
615
615
  )
616
- logger_use.addHandler(console_high_handler)
616
+ logger_use.addHandler(console_high_and_exc_handler)
617
617
 
618
618
  # debug & info
619
619
  directory = resolve_path(path=files_dir) # skipif-ci-and-windows
@@ -640,7 +640,7 @@ def setup_logging(
640
640
  standalone_file_handler = StandaloneFileHandler( # skipif-ci-and-windows
641
641
  level=ERROR, path=directory.joinpath("errors")
642
642
  )
643
- add_filters(standalone_file_handler, lambda x: x.exc_info is not None)
643
+ add_filters(standalone_file_handler, _standalone_file_filter)
644
644
  standalone_file_handler.setFormatter(
645
645
  RichTracebackFormatter(
646
646
  version=formatter_version,
@@ -660,6 +660,16 @@ def setup_logging(
660
660
  extra(logger_use)
661
661
 
662
662
 
663
+ def _console_low_or_no_exc_filter(record: LogRecord, /) -> bool:
664
+ return (record.levelno < ERROR) or (
665
+ (record.levelno >= ERROR) and (record.exc_info is None)
666
+ )
667
+
668
+
669
+ def _standalone_file_filter(record: LogRecord, /) -> bool:
670
+ return record.exc_info is not None
671
+
672
+
663
673
  ##
664
674
 
665
675
 
utilities/slack_sdk.py CHANGED
@@ -9,7 +9,12 @@ from typing import TYPE_CHECKING, override
9
9
 
10
10
  from slack_sdk.webhook.async_client import AsyncWebhookClient
11
11
 
12
- from utilities.asyncio import QueueProcessor, sleep_dur, timeout_dur
12
+ from utilities.asyncio import (
13
+ InfiniteQueueLooper,
14
+ QueueProcessor,
15
+ sleep_dur,
16
+ timeout_dur,
17
+ )
13
18
  from utilities.datetime import MINUTE, SECOND, datetime_duration_to_float
14
19
  from utilities.functools import cache
15
20
  from utilities.math import safe_round
@@ -95,6 +100,49 @@ class SlackHandler(Handler, QueueProcessor[str]):
95
100
  await sleep_dur(duration=self.sleep)
96
101
 
97
102
 
103
+ @dataclass(init=False, unsafe_hash=True)
104
+ class SlackHandlerIQL(Handler, InfiniteQueueLooper[None, str]):
105
+ """Handler for sending messages to Slack."""
106
+
107
+ @override
108
+ def __init__(
109
+ self,
110
+ url: str,
111
+ /,
112
+ *,
113
+ level: int = NOTSET,
114
+ sleep_core: Duration = _SLEEP,
115
+ sleep_restart: Duration = _SLEEP,
116
+ queue_type: type[Queue[str]] = Queue,
117
+ sender: Callable[[str, str], Coroutine1[None]] = _send_adapter,
118
+ timeout: Duration = _TIMEOUT,
119
+ ) -> None:
120
+ InfiniteQueueLooper.__init__( # InfiniteQueueLooper first
121
+ self, queue_type=queue_type
122
+ )
123
+ InfiniteQueueLooper.__post_init__(self)
124
+ Handler.__init__(self, level=level)
125
+ self.url = url
126
+ self.sender = sender
127
+ self.timeout = timeout
128
+ self.sleep_core = sleep_core
129
+ self.sleep_restart = sleep_restart
130
+
131
+ @override
132
+ def emit(self, record: LogRecord) -> None:
133
+ try:
134
+ self.put_items_nowait(self.format(record))
135
+ except Exception: # noqa: BLE001 # pragma: no cover
136
+ self.handleError(record)
137
+
138
+ @override
139
+ async def _process_items(self, *items: str) -> None:
140
+ """Process the first item."""
141
+ text = "\n".join(items)
142
+ async with timeout_dur(duration=self.timeout):
143
+ await self.sender(self.url, text)
144
+
145
+
98
146
  ##
99
147
 
100
148
 
@@ -128,4 +176,4 @@ def _get_client(url: str, /, *, timeout: Duration = _TIMEOUT) -> AsyncWebhookCli
128
176
  return AsyncWebhookClient(url, timeout=timeout_use)
129
177
 
130
178
 
131
- __all__ = ["SendToSlackError", "SlackHandler", "send_to_slack"]
179
+ __all__ = ["SendToSlackError", "SlackHandler", "SlackHandlerIQL", "send_to_slack"]
utilities/traceback.py CHANGED
@@ -60,6 +60,9 @@ _CALL_ARGS = "_CALL_ARGS"
60
60
  _INDENT = 4 * " "
61
61
 
62
62
 
63
+ ##
64
+
65
+
63
66
  class RichTracebackFormatter(Formatter):
64
67
  """Formatter for rich tracebacks."""
65
68
 
@@ -162,10 +165,17 @@ class RichTracebackFormatter(Formatter):
162
165
  detail=detail,
163
166
  post=post,
164
167
  )
165
- handler.addFilter(lambda r: r.exc_info is not None)
168
+ handler.addFilter(cls._has_exc_info)
166
169
  handler.setFormatter(formatter)
167
170
  return formatter
168
171
 
172
+ @classmethod
173
+ def _has_exc_info(cls, record: LogRecord, /) -> bool:
174
+ return record.exc_info is not None
175
+
176
+
177
+ ##
178
+
169
179
 
170
180
  @dataclass(repr=False, kw_only=True, slots=True)
171
181
  class _CallArgs:
@@ -470,6 +480,9 @@ class _Frame:
470
480
  return indent("\n".join(lines), depth * _INDENT)
471
481
 
472
482
 
483
+ ##
484
+
485
+
473
486
  def get_rich_traceback(
474
487
  error: TBaseException,
475
488
  /,