dycw-utilities 0.121.0__py3-none-any.whl → 0.122.0__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,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.121.0
3
+ Version: 0.122.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: typing-extensions<4.14,>=4.13.1
8
8
  Provides-Extra: test
9
- Requires-Dist: hypothesis<6.132,>=6.131.20; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.21; extra == 'test'
10
10
  Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
11
11
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
12
12
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -24,9 +24,9 @@ Provides-Extra: zzz-test-altair
24
24
  Requires-Dist: altair<5.6,>=5.5.0; extra == 'zzz-test-altair'
25
25
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-altair'
26
26
  Requires-Dist: img2pdf<0.7,>=0.6.0; extra == 'zzz-test-altair'
27
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-altair'
27
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-altair'
28
28
  Requires-Dist: vl-convert-python<1.8,>=1.7.0; extra == 'zzz-test-altair'
29
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-altair'
29
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-altair'
30
30
  Provides-Extra: zzz-test-astor
31
31
  Requires-Dist: astor<0.9,>=0.8.1; extra == 'zzz-test-astor'
32
32
  Provides-Extra: zzz-test-asyncio
@@ -37,9 +37,9 @@ Requires-Dist: atools<0.15,>=0.14.2; extra == 'zzz-test-atools'
37
37
  Provides-Extra: zzz-test-cachetools
38
38
  Requires-Dist: cachetools<5.6,>=5.5.2; extra == 'zzz-test-cachetools'
39
39
  Provides-Extra: zzz-test-click
40
- Requires-Dist: click<8.3,>=8.2.0; extra == 'zzz-test-click'
40
+ Requires-Dist: click<8.3,>=8.2.1; extra == 'zzz-test-click'
41
41
  Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-click'
42
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-click'
42
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-click'
43
43
  Provides-Extra: zzz-test-contextlib
44
44
  Provides-Extra: zzz-test-contextvars
45
45
  Provides-Extra: zzz-test-cryptography
@@ -48,11 +48,11 @@ Provides-Extra: zzz-test-cvxpy
48
48
  Requires-Dist: cvxpy<1.7,>=1.6.5; extra == 'zzz-test-cvxpy'
49
49
  Provides-Extra: zzz-test-dataclasses
50
50
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-dataclasses'
51
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-dataclasses'
52
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-dataclasses'
51
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-dataclasses'
52
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-dataclasses'
53
53
  Provides-Extra: zzz-test-datetime
54
54
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-datetime'
55
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-datetime'
55
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-datetime'
56
56
  Provides-Extra: zzz-test-enum
57
57
  Provides-Extra: zzz-test-errors
58
58
  Provides-Extra: zzz-test-eventkit
@@ -70,17 +70,17 @@ Provides-Extra: zzz-test-getpass
70
70
  Provides-Extra: zzz-test-git
71
71
  Provides-Extra: zzz-test-hashlib
72
72
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-hashlib'
73
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-hashlib'
74
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-hashlib'
73
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-hashlib'
74
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-hashlib'
75
75
  Provides-Extra: zzz-test-http
76
76
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-http'
77
77
  Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-http'
78
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-http'
78
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-http'
79
79
  Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
81
81
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
83
- Requires-Dist: hypothesis<6.132,>=6.131.20; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.21; extra == 'zzz-test-hypothesis'
84
84
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
85
85
  Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-hypothesis'
86
86
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
@@ -88,16 +88,16 @@ Requires-Dist: redis<6.2,>=6.1.0; extra == 'zzz-test-hypothesis'
88
88
  Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-hypothesis'
89
89
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-hypothesis'
90
90
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-hypothesis'
91
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-hypothesis'
91
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-hypothesis'
92
92
  Provides-Extra: zzz-test-ipython
93
93
  Requires-Dist: ipython<9.1,>=9.0.1; extra == 'zzz-test-ipython'
94
94
  Provides-Extra: zzz-test-iterables
95
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-iterables'
96
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-iterables'
95
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-iterables'
96
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-iterables'
97
97
  Provides-Extra: zzz-test-jupyter
98
98
  Requires-Dist: jupyterlab<4.3,>=4.2.0; extra == 'zzz-test-jupyter'
99
99
  Requires-Dist: pandas<2.3,>=2.2.2; extra == 'zzz-test-jupyter'
100
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-jupyter'
100
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-jupyter'
101
101
  Provides-Extra: zzz-test-logging
102
102
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-logging'
103
103
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'zzz-test-logging'
@@ -105,12 +105,12 @@ Requires-Dist: concurrent-log-handler<0.10,>=0.9.26; extra == 'zzz-test-logging'
105
105
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-logging'
106
106
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-logging'
107
107
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-logging'
108
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-logging'
108
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-logging'
109
109
  Provides-Extra: zzz-test-loguru
110
110
  Requires-Dist: loguru<0.8,>=0.7.3; extra == 'zzz-test-loguru'
111
111
  Provides-Extra: zzz-test-luigi
112
112
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-luigi'
113
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-luigi'
113
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-luigi'
114
114
  Provides-Extra: zzz-test-math
115
115
  Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-math'
116
116
  Provides-Extra: zzz-test-memory-profiler
@@ -121,24 +121,24 @@ Requires-Dist: more-itertools<10.8,>=10.7.0; extra == 'zzz-test-more-itertools'
121
121
  Provides-Extra: zzz-test-numpy
122
122
  Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-numpy'
123
123
  Provides-Extra: zzz-test-operator
124
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-operator'
125
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-operator'
124
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-operator'
125
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-operator'
126
126
  Provides-Extra: zzz-test-optuna
127
127
  Requires-Dist: optuna<4.4,>=4.3.0; extra == 'zzz-test-optuna'
128
128
  Provides-Extra: zzz-test-orjson
129
129
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-orjson'
130
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-orjson'
130
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-orjson'
131
131
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-orjson'
132
132
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-orjson'
133
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-orjson'
133
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-orjson'
134
134
  Provides-Extra: zzz-test-os
135
135
  Provides-Extra: zzz-test-pathlib
136
136
  Provides-Extra: zzz-test-pickle
137
137
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pickle'
138
138
  Provides-Extra: zzz-test-platform
139
139
  Provides-Extra: zzz-test-polars
140
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-polars'
141
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-polars'
140
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-polars'
141
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-polars'
142
142
  Provides-Extra: zzz-test-pqdm
143
143
  Requires-Dist: pqdm<0.3,>=0.2.0; extra == 'zzz-test-pqdm'
144
144
  Provides-Extra: zzz-test-pydantic
@@ -153,22 +153,22 @@ Requires-Dist: pyrsistent<0.21,>=0.20.0; extra == 'zzz-test-pyrsistent'
153
153
  Provides-Extra: zzz-test-pytest
154
154
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pytest'
155
155
  Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-pytest'
156
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-pytest'
156
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-pytest'
157
157
  Provides-Extra: zzz-test-pytest-regressions
158
158
  Requires-Dist: pytest-regressions<2.8,>=2.7.0; extra == 'zzz-test-pytest-regressions'
159
159
  Provides-Extra: zzz-test-python-dotenv
160
160
  Requires-Dist: python-dotenv<1.2,>=1.1.0; extra == 'zzz-test-python-dotenv'
161
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-python-dotenv'
161
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-python-dotenv'
162
162
  Provides-Extra: zzz-test-random
163
163
  Provides-Extra: zzz-test-re
164
164
  Provides-Extra: zzz-test-redis
165
165
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-redis'
166
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-redis'
166
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-redis'
167
167
  Requires-Dist: redis<6.2,>=6.1.0; extra == 'zzz-test-redis'
168
168
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-redis'
169
169
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-redis'
170
170
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-redis'
171
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-redis'
171
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-redis'
172
172
  Provides-Extra: zzz-test-rich
173
173
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-rich'
174
174
  Provides-Extra: zzz-test-scipy
@@ -191,10 +191,10 @@ Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-sqlalchemy-polars'
191
191
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-sqlalchemy-polars'
192
192
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-sqlalchemy-polars'
193
193
  Requires-Dist: nest-asyncio<1.7,>=1.6.0; extra == 'zzz-test-sqlalchemy-polars'
194
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-sqlalchemy-polars'
194
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-sqlalchemy-polars'
195
195
  Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-sqlalchemy-polars'
196
196
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-sqlalchemy-polars'
197
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-sqlalchemy-polars'
197
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-sqlalchemy-polars'
198
198
  Provides-Extra: zzz-test-streamlit
199
199
  Requires-Dist: streamlit<1.46,>=1.45.0; extra == 'zzz-test-streamlit'
200
200
  Provides-Extra: zzz-test-sys
@@ -202,7 +202,7 @@ Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-sys'
202
202
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-sys'
203
203
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-sys'
204
204
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-sys'
205
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-sys'
205
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-sys'
206
206
  Provides-Extra: zzz-test-tempfile
207
207
  Provides-Extra: zzz-test-tenacity
208
208
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-tenacity'
@@ -213,11 +213,11 @@ Provides-Extra: zzz-test-traceback
213
213
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-traceback'
214
214
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-traceback'
215
215
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-traceback'
216
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-traceback'
216
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-traceback'
217
217
  Provides-Extra: zzz-test-types
218
218
  Provides-Extra: zzz-test-typing
219
- Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-typing'
220
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-typing'
219
+ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-typing'
220
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-typing'
221
221
  Provides-Extra: zzz-test-tzlocal
222
222
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-tzlocal'
223
223
  Provides-Extra: zzz-test-uuid
@@ -225,11 +225,11 @@ Provides-Extra: zzz-test-version
225
225
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-version'
226
226
  Provides-Extra: zzz-test-warnings
227
227
  Provides-Extra: zzz-test-whenever
228
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-whenever'
228
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-whenever'
229
229
  Provides-Extra: zzz-test-zipfile
230
230
  Provides-Extra: zzz-test-zoneinfo
231
231
  Requires-Dist: tzdata<2025.3,>=2025.2; extra == 'zzz-test-zoneinfo'
232
- Requires-Dist: whenever<0.9,>=0.8.0; extra == 'zzz-test-zoneinfo'
232
+ Requires-Dist: whenever<0.9,>=0.8.2; extra == 'zzz-test-zoneinfo'
233
233
  Description-Content-Type: text/markdown
234
234
 
235
235
  [![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities)
@@ -1,7 +1,7 @@
1
- utilities/__init__.py,sha256=FKqdZTwX6QRMMK9ehPxm3uM8OyIJA4Mgay0d8pgY6Nw,60
1
+ utilities/__init__.py,sha256=V19RNamTpOs4KxZ_wh7w8SsT-d-C24iIQHK07Pu3opA,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=gn8dxBsNzFfFzrNpImF731LAmgLwEQM3uiwKuUPlg08,18822
4
+ utilities/asyncio.py,sha256=sVCGCGl5vGo1MztLcd46DiuWBR6XQW1CsGDyDdaevYg,22766
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
@@ -16,7 +16,7 @@ utilities/datetime.py,sha256=uYoaOi_C1YtNXGfTN9xlTrW62Re2b1_4Skuv14_MeYQ,38985
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=gxsaa7eq7jbYl41Of40-ivjXqJB5gt4QAcJ0smZZMJE,829
18
18
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
19
- utilities/fastapi.py,sha256=eiisloI6kQVCkPfDpBzlLrDZDi8yJ0VmrSPlJ2k84Mo,2334
19
+ utilities/fastapi.py,sha256=LG1-Q8RDi7wsyVN6v74qptPYX8WGXPkFOQFniMvtzjc,2439
20
20
  utilities/fpdf2.py,sha256=y1NGXR5chWqLXWpewGV3hlRGMr_5yV1lVRkPBhPEgJI,1843
21
21
  utilities/functions.py,sha256=jgt592voaHNtX56qX0SRvFveVCRmSIxCZmqvpLZCnY8,27305
22
22
  utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
@@ -47,7 +47,7 @@ utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
47
47
  utilities/period.py,sha256=o4wXYEXVlFomop4-Ra4L0yRP4i99NZFjIe_fa7NdZck,11024
48
48
  utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
49
49
  utilities/platform.py,sha256=NU7ycTvAXAG-fdYmDXaM1m4EOml2cGiaYwaUzfzSqyU,1767
50
- utilities/polars.py,sha256=fxfSm4xVHwKvRxu50IhYNKCKOagp12FdwsVf04ARKpk,63692
50
+ utilities/polars.py,sha256=tGO3r6S4lFtir6zbdIvZ_kvKBWIha4lnA1eEmV3EvNQ,63632
51
51
  utilities/polars_ols.py,sha256=efhXf0gjrHUpQrvS6a7g8yJQJWf_ATKtJnqqF2inCOU,5680
52
52
  utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
53
53
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -59,7 +59,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
59
59
  utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
60
60
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
61
61
  utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
62
- utilities/redis.py,sha256=i5G3k-EIi2JSf9arLIu0YW6viGsiYtxAoynbb4KvJek,26628
62
+ utilities/redis.py,sha256=8ELnXiISVHY1m9elJWhekhLXg6NQSaNIIPvZ_EaGw3s,26624
63
63
  utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
64
64
  utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
65
65
  utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
@@ -67,7 +67,7 @@ utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
67
67
  utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
68
68
  utilities/slack_sdk.py,sha256=A2f7-DYOngRoUP6ZdLIaUQ6Lfzgru5Xp3U3k5JfEkQE,3301
69
69
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
70
- utilities/sqlalchemy.py,sha256=iwNJyR0Dx47l81aHEq5P6eMzgduVxxGH531OVUp2k_A,35451
70
+ utilities/sqlalchemy.py,sha256=yCUCDhg0wFOCdEh6wwBD7Ma979OksLpz4arck1s653s,35447
71
71
  utilities/sqlalchemy_polars.py,sha256=wjJpoUo-yO9E2ujpG_06vV5r2OdvBiQ4yvV6wKCa2Tk,15605
72
72
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
73
73
  utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
@@ -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.121.0.dist-info/METADATA,sha256=YvumB9cX9CE9qNSb60yruXQ-NlHPyugeA91GzOJLPUM,12943
92
- dycw_utilities-0.121.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.121.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.121.0.dist-info/RECORD,,
91
+ dycw_utilities-0.122.0.dist-info/METADATA,sha256=VUsd4omUw1y-str8uasKFbLZUMA2Sr3HpSaq8f8coh0,12943
92
+ dycw_utilities-0.122.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.122.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.122.0.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.121.0"
3
+ __version__ = "0.122.0"
utilities/asyncio.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import datetime as dt
4
4
  from abc import ABC, abstractmethod
5
5
  from asyncio import (
6
+ CancelledError,
6
7
  Event,
7
8
  PriorityQueue,
8
9
  Queue,
@@ -12,11 +13,17 @@ from asyncio import (
12
13
  Task,
13
14
  TaskGroup,
14
15
  create_subprocess_shell,
16
+ create_task,
15
17
  sleep,
16
18
  timeout,
17
19
  )
18
20
  from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
19
- from contextlib import _AsyncGeneratorContextManager, asynccontextmanager
21
+ from contextlib import (
22
+ AsyncExitStack,
23
+ _AsyncGeneratorContextManager,
24
+ asynccontextmanager,
25
+ suppress,
26
+ )
20
27
  from dataclasses import dataclass, field
21
28
  from io import StringIO
22
29
  from logging import getLogger
@@ -27,6 +34,7 @@ from typing import (
27
34
  Any,
28
35
  Generic,
29
36
  NoReturn,
37
+ Self,
30
38
  TextIO,
31
39
  TypeVar,
32
40
  assert_never,
@@ -42,7 +50,7 @@ from utilities.datetime import (
42
50
  get_now,
43
51
  round_datetime,
44
52
  )
45
- from utilities.errors import repr_error
53
+ from utilities.errors import ImpossibleCaseError, repr_error
46
54
  from utilities.functions import ensure_int, ensure_not_none, get_class_name
47
55
  from utilities.reprlib import get_repr
48
56
  from utilities.sentinel import Sentinel, sentinel
@@ -60,6 +68,7 @@ if TYPE_CHECKING:
60
68
  from asyncio.subprocess import Process
61
69
  from collections.abc import AsyncIterator, Sequence
62
70
  from contextvars import Context
71
+ from types import TracebackType
63
72
 
64
73
  from utilities.types import Duration
65
74
 
@@ -125,26 +134,94 @@ class EnhancedTaskGroup(TaskGroup):
125
134
  class InfiniteLooper(ABC, Generic[THashable]):
126
135
  """An infinite loop which can throw exceptions by setting events."""
127
136
 
128
- sleep_core: DurationOrEveryDuration = SECOND
129
- sleep_restart: DurationOrEveryDuration = MINUTE
130
- logger: str | None = None
137
+ sleep_core: DurationOrEveryDuration = field(default=SECOND, repr=False)
138
+ sleep_restart: DurationOrEveryDuration = field(default=MINUTE, repr=False)
139
+ duration: Duration | None = field(default=None, repr=False)
140
+ logger: str | None = field(default=None, repr=False)
141
+ _await_upon_aenter: bool = field(default=True, init=False, repr=False)
142
+ _depth: int = field(default=0, init=False, repr=False)
131
143
  _events: Mapping[THashable | None, Event] = field(
132
144
  default_factory=dict, init=False, repr=False, hash=False
133
145
  )
146
+ _stack: AsyncExitStack = field(
147
+ default_factory=AsyncExitStack, init=False, repr=False
148
+ )
149
+ _task: Task[None] | None = field(default=None, init=False, repr=False)
134
150
 
135
151
  def __post_init__(self) -> None:
136
152
  self._events = {
137
153
  event: Event() for event, _ in self._yield_events_and_exceptions()
138
154
  }
139
155
 
140
- async def __call__(self) -> None:
141
- """Create a coroutine to run the looper."""
142
- coroutines = list(self._yield_coroutines())
143
- if len(coroutines) == 0:
144
- return await self._run_looper()
145
- return await self._run_looper_with_coroutines(*coroutines)
156
+ async def __aenter__(self) -> Self:
157
+ """Context manager entry."""
158
+ if (self._task is None) and (self._depth == 0):
159
+ _ = await self._stack.__aenter__()
160
+ self._task = create_task(self._run_looper())
161
+ if self._await_upon_aenter:
162
+ with suppress(CancelledError):
163
+ await self._task
164
+ elif (self._task is not None) and (self._depth >= 1):
165
+ ...
166
+ else:
167
+ raise ImpossibleCaseError( # pragma: no cover
168
+ case=[f"{self._task=}", f"{self._depth=}"]
169
+ )
170
+ self._depth += 1
171
+ return self
172
+
173
+ async def __aexit__(
174
+ self,
175
+ exc_type: type[BaseException] | None = None,
176
+ exc_value: BaseException | None = None,
177
+ traceback: TracebackType | None = None,
178
+ ) -> None:
179
+ """Context manager exit."""
180
+ _ = (exc_type, exc_value, traceback)
181
+ if (self._task is None) or (self._depth == 0):
182
+ raise ImpossibleCaseError( # pragma: no cover
183
+ case=[f"{self._task=}", f"{self._depth=}"]
184
+ )
185
+ self._depth -= 1
186
+ if self._depth == 0:
187
+ _ = await self._stack.__aexit__(exc_type, exc_value, traceback)
188
+ with suppress(CancelledError):
189
+ await self._task
190
+ self._task = None
191
+ with suppress(Exception):
192
+ await self._teardown()
193
+
194
+ async def stop(self) -> None:
195
+ """Stop the service."""
196
+ if self._task is None:
197
+ raise ImpossibleCaseError(case=[f"{self._task=}"]) # pragma: no cover
198
+ with suppress(CancelledError):
199
+ _ = self._task.cancel()
146
200
 
147
201
  async def _run_looper(self) -> None:
202
+ """Run the looper."""
203
+ match self.duration:
204
+ case None:
205
+ await self._run_looper_without_timeout()
206
+ case int() | float() | dt.timedelta() as duration:
207
+ try:
208
+ async with timeout_dur(duration=duration):
209
+ return await self._run_looper_without_timeout()
210
+ except TimeoutError:
211
+ await self.stop()
212
+ case _ as never:
213
+ assert_never(never)
214
+ return None
215
+
216
+ async def _run_looper_without_timeout(self) -> None:
217
+ """Run the looper without a timeout."""
218
+ coroutines = list(self._yield_coroutines())
219
+ loopers = list(self._yield_loopers())
220
+ if (len(coroutines) == 0) and (len(loopers) == 0):
221
+ return await self._run_looper_by_itself()
222
+ return await self._run_looper_with_others(coroutines, loopers)
223
+
224
+ async def _run_looper_by_itself(self) -> None:
148
225
  """Run the looper by itself."""
149
226
  while True:
150
227
  try:
@@ -171,20 +248,31 @@ class InfiniteLooper(ABC, Generic[THashable]):
171
248
  raise
172
249
  except Exception as error: # noqa: BLE001
173
250
  self._error_upon_core(error)
174
- await self._run_sleep(self.sleep_restart)
251
+ try:
252
+ await self._teardown()
253
+ except Exception as error: # noqa: BLE001
254
+ self._error_upon_teardown(error)
255
+ finally:
256
+ await self._run_sleep(self.sleep_restart)
175
257
 
176
- async def _run_looper_with_coroutines(
177
- self, *coroutines: Callable[[], Coroutine1[None]]
258
+ async def _run_looper_with_others(
259
+ self,
260
+ coroutines: Iterable[Callable[[], Coroutine1[None]]],
261
+ loopers: Iterable[InfiniteLooper[Any]],
262
+ /,
178
263
  ) -> None:
179
264
  """Run multiple loopers."""
180
265
  while True:
181
266
  self._reset_events()
182
267
  try:
183
- async with TaskGroup() as tg:
184
- _ = tg.create_task(self._run_looper())
268
+ async with TaskGroup() as tg, AsyncExitStack() as stack:
269
+ _ = tg.create_task(self._run_looper_by_itself())
185
270
  _ = [tg.create_task(c()) for c in coroutines]
271
+ _ = [
272
+ tg.create_task(stack.enter_async_context(lo)) for lo in loopers
273
+ ]
186
274
  except ExceptionGroup as error:
187
- self._error_group_upon_coroutines(error)
275
+ self._error_group_upon_others(error)
188
276
  await self._run_sleep(self.sleep_restart)
189
277
 
190
278
  async def _initialize(self) -> None:
@@ -193,6 +281,9 @@ class InfiniteLooper(ABC, Generic[THashable]):
193
281
  async def _core(self) -> None:
194
282
  """Run the core part of the loop."""
195
283
 
284
+ async def _teardown(self) -> None:
285
+ """Tear down the loop."""
286
+
196
287
  def _error_upon_initialize(self, error: Exception, /) -> None:
197
288
  """Handle any errors upon initializing the looper."""
198
289
  if self.logger is not None:
@@ -213,7 +304,17 @@ class InfiniteLooper(ABC, Generic[THashable]):
213
304
  self._sleep_restart_desc,
214
305
  )
215
306
 
216
- def _error_group_upon_coroutines(self, group: ExceptionGroup, /) -> None:
307
+ def _error_upon_teardown(self, error: Exception, /) -> None:
308
+ """Handle any errors upon tearing down the looper."""
309
+ if self.logger is not None:
310
+ getLogger(name=self.logger).error(
311
+ "%r encountered %r whilst tearing down; sleeping %s...",
312
+ get_class_name(self),
313
+ repr_error(error),
314
+ self._sleep_restart_desc,
315
+ )
316
+
317
+ def _error_group_upon_others(self, group: ExceptionGroup, /) -> None:
217
318
  """Handle any errors upon running the core function."""
218
319
  if self.logger is not None:
219
320
  errors = group.exceptions
@@ -269,15 +370,19 @@ class InfiniteLooper(ABC, Generic[THashable]):
269
370
  raise _InfiniteLooperNoSuchEventError(looper=self, event=event) from None
270
371
  event_obj.set()
271
372
 
373
+ def _yield_events_and_exceptions(
374
+ self,
375
+ ) -> Iterator[tuple[THashable | None, MaybeType[Exception]]]:
376
+ """Yield the events & exceptions."""
377
+ yield (None, _InfiniteLooperDefaultEventError(looper=self))
378
+
272
379
  def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
273
380
  """Yield any other coroutines which must also be run."""
274
381
  yield from []
275
382
 
276
- def _yield_events_and_exceptions(
277
- self,
278
- ) -> Iterator[tuple[THashable | None, MaybeType[BaseException]]]:
279
- """Yield the events & exceptions."""
280
- yield (None, _InfiniteLooperDefaultEventError)
383
+ def _yield_loopers(self) -> Iterator[InfiniteLooper[Any]]:
384
+ """Yield any other loopers which must also be run."""
385
+ yield from []
281
386
 
282
387
 
283
388
  @dataclass(kw_only=True, slots=True)
@@ -309,6 +414,7 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
309
414
  """An infinite loop which processes a queue."""
310
415
 
311
416
  queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
417
+ _await_upon_aenter: bool = field(default=False, init=False, repr=False)
312
418
  _queue: Queue[_T] = field(init=False)
313
419
 
314
420
  @override
@@ -346,6 +452,7 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
346
452
  """Run until the queue is empty."""
347
453
  while not self.empty():
348
454
  await self._process_items(*get_items_nowait(self._queue))
455
+ await self.stop()
349
456
 
350
457
  @override
351
458
  def _error_upon_core(self, error: Exception, /) -> None:
@@ -353,12 +460,12 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
353
460
  if self.logger is not None:
354
461
  if isinstance(error, InfiniteQueueLooperError):
355
462
  getLogger(name=self.logger).error(
356
- "%r encountered %s whilst processing %d item(s) %s; sleeping for %s...",
463
+ "%r encountered %s whilst processing %d item(s) %s; sleeping %s...",
357
464
  get_class_name(self),
358
465
  repr_error(error.error),
359
466
  len(error.items),
360
467
  get_repr(error.items),
361
- self.sleep_restart,
468
+ self._sleep_restart_desc,
362
469
  )
363
470
  else:
364
471
  super()._error_upon_core(error) # pragma: no cover
@@ -370,10 +477,6 @@ class InfiniteQueueLooperError(Exception, Generic[_T]):
370
477
  items: Sequence[_T]
371
478
  error: Exception
372
479
 
373
- @override
374
- def __str__(self) -> str:
375
- return f"{get_class_name(self.looper)!r} encountered {repr_error(self.error)} whilst processing {len(self.items)} item(s): {get_repr(self.items)}"
376
-
377
480
 
378
481
  ##
379
482
 
utilities/fastapi.py CHANGED
@@ -70,5 +70,9 @@ class PingReceiver(InfiniteLooper):
70
70
  async def _initialize(self) -> None:
71
71
  await self._server.serve() # skipif-ci
72
72
 
73
+ @override
74
+ async def _teardown(self) -> None:
75
+ await self._server.shutdown() # skipif-ci
76
+
73
77
 
74
78
  __all__ = ["PingReceiver"]
utilities/polars.py CHANGED
@@ -117,7 +117,7 @@ if TYPE_CHECKING:
117
117
  JoinStrategy, # pyright: ignore[reportPrivateImportUsage]
118
118
  JoinValidation, # pyright: ignore[reportPrivateImportUsage]
119
119
  PolarsDataType, # pyright: ignore[reportPrivateImportUsage]
120
- RollingInterpolationMethod, # pyright: ignore[reportPrivateImportUsage]
120
+ QuantileMethod, # pyright: ignore[reportPrivateImportUsage]
121
121
  SchemaDict, # pyright: ignore[reportPrivateImportUsage]
122
122
  TimeUnit, # pyright: ignore[reportPrivateImportUsage]
123
123
  )
@@ -939,7 +939,7 @@ def cross_rolling_quantile(
939
939
  quantile: float,
940
940
  /,
941
941
  *,
942
- interpolation: RollingInterpolationMethod = "nearest",
942
+ interpolation: QuantileMethod = "nearest",
943
943
  window_size: int = 2,
944
944
  weights: list[float] | None = None,
945
945
  min_samples: int | None = None,
@@ -952,7 +952,7 @@ def cross_rolling_quantile(
952
952
  quantile: float,
953
953
  /,
954
954
  *,
955
- interpolation: RollingInterpolationMethod = "nearest",
955
+ interpolation: QuantileMethod = "nearest",
956
956
  window_size: int = 2,
957
957
  weights: list[float] | None = None,
958
958
  min_samples: int | None = None,
@@ -965,7 +965,7 @@ def cross_rolling_quantile(
965
965
  quantile: float,
966
966
  /,
967
967
  *,
968
- interpolation: RollingInterpolationMethod = "nearest",
968
+ interpolation: QuantileMethod = "nearest",
969
969
  window_size: int = 2,
970
970
  weights: list[float] | None = None,
971
971
  min_samples: int | None = None,
@@ -977,7 +977,7 @@ def cross_rolling_quantile(
977
977
  quantile: float,
978
978
  /,
979
979
  *,
980
- interpolation: RollingInterpolationMethod = "nearest",
980
+ interpolation: QuantileMethod = "nearest",
981
981
  window_size: int = 2,
982
982
  weights: list[float] | None = None,
983
983
  min_samples: int | None = None,
utilities/redis.py CHANGED
@@ -611,7 +611,7 @@ class Publisher(InfiniteQueueLooper[None, tuple[str, EncodableT]]):
611
611
  @override
612
612
  def _yield_events_and_exceptions(
613
613
  self,
614
- ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
614
+ ) -> Iterator[tuple[None, MaybeType[Exception]]]:
615
615
  yield (None, PublisherError) # skipif-ci-and-not-linux
616
616
 
617
617
 
utilities/sqlalchemy.py CHANGED
@@ -640,7 +640,7 @@ class Upserter(InfiniteQueueLooper[None, _InsertItem]):
640
640
  @override
641
641
  def _yield_events_and_exceptions(
642
642
  self,
643
- ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
643
+ ) -> Iterator[tuple[None, MaybeType[Exception]]]:
644
644
  yield (None, UpserterError)
645
645
 
646
646