dycw-utilities 0.131.1__py3-none-any.whl → 0.131.3__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,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.131.1
3
+ Version: 0.131.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
+ Requires-Dist: atomicwrites<1.5,>=1.4.1
7
8
  Requires-Dist: typing-extensions<4.15,>=4.14.0
9
+ Requires-Dist: tzlocal<5.4,>=5.3.1
10
+ Requires-Dist: whenever<0.9,>=0.8.5
8
11
  Provides-Extra: logging
9
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'logging'
10
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
11
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'logging'
12
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'logging'
13
13
  Provides-Extra: test
14
14
  Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
15
15
  Requires-Dist: hypothesis<6.136,>=6.135.2; extra == 'test'
@@ -28,14 +28,11 @@ Provides-Extra: zzz-test-aiolimiter
28
28
  Requires-Dist: aiolimiter<1.3,>=1.2.1; extra == 'zzz-test-aiolimiter'
29
29
  Provides-Extra: zzz-test-altair
30
30
  Requires-Dist: altair<5.6,>=5.5.0; extra == 'zzz-test-altair'
31
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-altair'
32
31
  Requires-Dist: img2pdf<0.7,>=0.6.0; extra == 'zzz-test-altair'
33
32
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-altair'
34
33
  Requires-Dist: vl-convert-python<1.9,>=1.8.0; extra == 'zzz-test-altair'
35
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-altair'
36
34
  Provides-Extra: zzz-test-asyncio
37
35
  Provides-Extra: zzz-test-atomicwrites
38
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-atomicwrites'
39
36
  Provides-Extra: zzz-test-atools
40
37
  Requires-Dist: atools<0.15,>=0.14.2; extra == 'zzz-test-atools'
41
38
  Provides-Extra: zzz-test-cachetools
@@ -43,7 +40,6 @@ Requires-Dist: cachetools<5.6,>=5.5.2; extra == 'zzz-test-cachetools'
43
40
  Provides-Extra: zzz-test-click
44
41
  Requires-Dist: click<8.3,>=8.2.1; extra == 'zzz-test-click'
45
42
  Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-click'
46
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-click'
47
43
  Provides-Extra: zzz-test-contextlib
48
44
  Provides-Extra: zzz-test-contextvars
49
45
  Provides-Extra: zzz-test-cryptography
@@ -53,10 +49,7 @@ Requires-Dist: cvxpy<1.7,>=1.6.5; extra == 'zzz-test-cvxpy'
53
49
  Provides-Extra: zzz-test-dataclasses
54
50
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-dataclasses'
55
51
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-dataclasses'
56
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-dataclasses'
57
52
  Provides-Extra: zzz-test-datetime
58
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-datetime'
59
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-datetime'
60
53
  Provides-Extra: zzz-test-enum
61
54
  Provides-Extra: zzz-test-errors
62
55
  Provides-Extra: zzz-test-eventkit
@@ -67,7 +60,6 @@ Requires-Dist: httpx<0.29,>=0.28.1; extra == 'zzz-test-fastapi'
67
60
  Requires-Dist: uvicorn<0.35,>=0.34.1; extra == 'zzz-test-fastapi'
68
61
  Provides-Extra: zzz-test-fpdf2
69
62
  Requires-Dist: fpdf2<2.9,>=2.8.3; extra == 'zzz-test-fpdf2'
70
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-fpdf2'
71
63
  Provides-Extra: zzz-test-functions
72
64
  Provides-Extra: zzz-test-functools
73
65
  Provides-Extra: zzz-test-getpass
@@ -75,11 +67,8 @@ Provides-Extra: zzz-test-git
75
67
  Provides-Extra: zzz-test-hashlib
76
68
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-hashlib'
77
69
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-hashlib'
78
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-hashlib'
79
70
  Provides-Extra: zzz-test-http
80
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-http'
81
71
  Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-http'
82
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-http'
83
72
  Provides-Extra: zzz-test-hypothesis
84
73
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
85
74
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
@@ -91,27 +80,20 @@ Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
91
80
  Requires-Dist: redis<6.3,>=6.2.0; extra == 'zzz-test-hypothesis'
92
81
  Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-hypothesis'
93
82
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-hypothesis'
94
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-hypothesis'
95
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-hypothesis'
96
83
  Provides-Extra: zzz-test-ipython
97
84
  Requires-Dist: ipython<9.1,>=9.0.1; extra == 'zzz-test-ipython'
98
85
  Provides-Extra: zzz-test-iterables
99
86
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-iterables'
100
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-iterables'
101
87
  Provides-Extra: zzz-test-jupyter
102
88
  Requires-Dist: jupyterlab<4.3,>=4.2.0; extra == 'zzz-test-jupyter'
103
89
  Requires-Dist: pandas<2.4,>=2.3.0; extra == 'zzz-test-jupyter'
104
90
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-jupyter'
105
91
  Provides-Extra: zzz-test-logging
106
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-logging'
107
92
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'zzz-test-logging'
108
93
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-logging'
109
94
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-logging'
110
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-logging'
111
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-logging'
112
95
  Provides-Extra: zzz-test-luigi
113
96
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-luigi'
114
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-luigi'
115
97
  Provides-Extra: zzz-test-math
116
98
  Requires-Dist: numpy<2.4,>=2.3.0; extra == 'zzz-test-math'
117
99
  Provides-Extra: zzz-test-memory-profiler
@@ -123,43 +105,32 @@ Provides-Extra: zzz-test-numpy
123
105
  Requires-Dist: numpy<2.4,>=2.3.0; extra == 'zzz-test-numpy'
124
106
  Provides-Extra: zzz-test-operator
125
107
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-operator'
126
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-operator'
127
108
  Provides-Extra: zzz-test-optuna
128
109
  Requires-Dist: optuna<4.4,>=4.3.0; extra == 'zzz-test-optuna'
129
110
  Provides-Extra: zzz-test-orjson
130
111
  Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-orjson'
131
112
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-orjson'
132
113
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-orjson'
133
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-orjson'
134
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-orjson'
135
114
  Provides-Extra: zzz-test-os
136
115
  Provides-Extra: zzz-test-pathlib
137
116
  Provides-Extra: zzz-test-pickle
138
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pickle'
139
117
  Provides-Extra: zzz-test-platform
140
118
  Provides-Extra: zzz-test-polars
141
119
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-polars'
142
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-polars'
143
120
  Provides-Extra: zzz-test-pqdm
144
121
  Requires-Dist: pqdm<0.3,>=0.2.0; extra == 'zzz-test-pqdm'
145
122
  Provides-Extra: zzz-test-pydantic
146
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pydantic'
147
123
  Requires-Dist: pydantic<2.12,>=2.11.4; extra == 'zzz-test-pydantic'
148
124
  Provides-Extra: zzz-test-pyinstrument
149
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pyinstrument'
150
125
  Requires-Dist: pyinstrument<5.1,>=5.0.2; extra == 'zzz-test-pyinstrument'
151
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-pyinstrument'
152
126
  Provides-Extra: zzz-test-pyrsistent
153
127
  Requires-Dist: pyrsistent<0.21,>=0.20.0; extra == 'zzz-test-pyrsistent'
154
128
  Provides-Extra: zzz-test-pytest
155
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pytest'
156
129
  Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-pytest'
157
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-pytest'
158
130
  Provides-Extra: zzz-test-pytest-regressions
159
131
  Requires-Dist: pytest-regressions<2.9,>=2.8.0; extra == 'zzz-test-pytest-regressions'
160
132
  Provides-Extra: zzz-test-python-dotenv
161
133
  Requires-Dist: python-dotenv<1.2,>=1.1.0; extra == 'zzz-test-python-dotenv'
162
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-python-dotenv'
163
134
  Provides-Extra: zzz-test-random
164
135
  Provides-Extra: zzz-test-re
165
136
  Provides-Extra: zzz-test-redis
@@ -168,8 +139,6 @@ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-redis'
168
139
  Requires-Dist: redis<6.3,>=6.2.0; extra == 'zzz-test-redis'
169
140
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-redis'
170
141
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-redis'
171
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-redis'
172
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-redis'
173
142
  Provides-Extra: zzz-test-rich
174
143
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-rich'
175
144
  Provides-Extra: zzz-test-scipy
@@ -195,15 +164,11 @@ Requires-Dist: nest-asyncio<1.7,>=1.6.0; extra == 'zzz-test-sqlalchemy-polars'
195
164
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-sqlalchemy-polars'
196
165
  Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-sqlalchemy-polars'
197
166
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-sqlalchemy-polars'
198
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-sqlalchemy-polars'
199
167
  Provides-Extra: zzz-test-streamlit
200
168
  Requires-Dist: streamlit<1.46,>=1.45.0; extra == 'zzz-test-streamlit'
201
169
  Provides-Extra: zzz-test-sys
202
- Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-sys'
203
170
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-sys'
204
171
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-sys'
205
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-sys'
206
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-sys'
207
172
  Provides-Extra: zzz-test-tempfile
208
173
  Provides-Extra: zzz-test-tenacity
209
174
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-tenacity'
@@ -213,24 +178,18 @@ Provides-Extra: zzz-test-timer
213
178
  Provides-Extra: zzz-test-traceback
214
179
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-traceback'
215
180
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-traceback'
216
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-traceback'
217
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-traceback'
218
181
  Provides-Extra: zzz-test-types
219
182
  Provides-Extra: zzz-test-typing
220
183
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-typing'
221
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-typing'
222
184
  Provides-Extra: zzz-test-tzlocal
223
- Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-tzlocal'
224
185
  Provides-Extra: zzz-test-uuid
225
186
  Provides-Extra: zzz-test-version
226
187
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-version'
227
188
  Provides-Extra: zzz-test-warnings
228
189
  Provides-Extra: zzz-test-whenever
229
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-whenever'
230
190
  Provides-Extra: zzz-test-zipfile
231
191
  Provides-Extra: zzz-test-zoneinfo
232
192
  Requires-Dist: tzdata<2025.3,>=2025.2; extra == 'zzz-test-zoneinfo'
233
- Requires-Dist: whenever<0.9,>=0.8.5; extra == 'zzz-test-zoneinfo'
234
193
  Description-Content-Type: text/markdown
235
194
 
236
195
  [![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities)
@@ -1,6 +1,6 @@
1
- utilities/__init__.py,sha256=7cvfTXJSLM1ajMZOJ6keAbIOlGbRAUMdkqIQoooZ6Bk,60
1
+ utilities/__init__.py,sha256=V2NCsw2lQ18h6ZVmnJk8Ff3KYOijFAMl6rMmt_TUQJk,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
- utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
+ utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/asyncio.py,sha256=lvdgBhuMtxq0dpiwF9g2WMMrit3kqXibN1V5NZ4xdbo,38046
5
5
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
6
6
  utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
@@ -16,23 +16,23 @@ utilities/datetime.py,sha256=aiPh2OZK2g9gn4yEeSO0lODOmvx8U_rGn6XeSzyk4VY,38738
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
18
18
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
19
- utilities/fastapi.py,sha256=gZrXYxKAc7ZEAL_tDmkcbqebkm-KfMCY0X8r-1HF5dI,2962
20
- utilities/fpdf2.py,sha256=y1NGXR5chWqLXWpewGV3hlRGMr_5yV1lVRkPBhPEgJI,1843
19
+ utilities/fastapi.py,sha256=8ABDSOH99j7H8cpKLZhUT2JzU18wiBqcTQuBUlTe-QE,2937
20
+ utilities/fpdf2.py,sha256=lqizPXpzdwfJk3ChcbbWVa7WNXO2uvy7KDzWQtVTnXA,1831
21
21
  utilities/functions.py,sha256=jgt592voaHNtX56qX0SRvFveVCRmSIxCZmqvpLZCnY8,27305
22
22
  utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
23
23
  utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
24
24
  utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
25
25
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
26
26
  utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
27
- utilities/hypothesis.py,sha256=y6a5Ilokrlvu7hNpPxUFWKy5p7vra1Oe6yEj1g-1Fng,42747
27
+ utilities/hypothesis.py,sha256=Wpw_jX9TVAXAI13JuPkYbMYlLod9_o7V51x79hhZabU,47332
28
28
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
29
29
  utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
30
30
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
31
31
  utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
32
32
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
33
33
  utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
34
- utilities/lightweight_charts.py,sha256=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
35
- utilities/logging.py,sha256=0dUW0F0RISy9arU58M6WHn7ACSs3-S4GHDs8ZCkjyNk,18420
34
+ utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
35
+ utilities/logging.py,sha256=9XpYHrSVnW2_wYTWQb7LllVxcgq-U2vjUIlM4M60LCE,18407
36
36
  utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
37
37
  utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
38
38
  utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,962
@@ -41,12 +41,12 @@ utilities/more_itertools.py,sha256=tBbjjKx8_Uv_TCjxhPwrGfAx_jRHtvLIZqXVWAsjzqA,8
41
41
  utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
42
42
  utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
43
43
  utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
44
- utilities/orjson.py,sha256=AvPFxzJdxC-3PBID3cqdiMyN8FeC7aW9QUgGwbvKuAM,36948
44
+ utilities/orjson.py,sha256=vmPsuOOxrQBAg6aEVVKHfOX9A04QlSa162El5HrIT9E,36889
45
45
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
46
46
  utilities/parse.py,sha256=vsZ2jf_ceSI_Kta9titixufysJaVXh0Whjz1T4awJZw,18938
47
47
  utilities/pathlib.py,sha256=PK41rf1c9Wqv7h8f5R7H3_Lhq_gQZTUJD5tu3gMHVaU,3247
48
48
  utilities/period.py,sha256=o4wXYEXVlFomop4-Ra4L0yRP4i99NZFjIe_fa7NdZck,11024
49
- utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
49
+ 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
@@ -54,10 +54,10 @@ utilities/pottery.py,sha256=-YikdIQFqhFXifyMq5R5z6V6N8y639gAnVVXR4jXxdc,3568
54
54
  utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
55
55
  utilities/psutil.py,sha256=RtbLKOoIJhqrJmEoHDBVeSD-KPzshtS0FtRXBP9_w2s,3751
56
56
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- utilities/pydantic.py,sha256=f6qtR5mO2YMuyvNmbaEj5YeD9eGA4YYfb7Bjzh9jUs0,1845
58
- utilities/pyinstrument.py,sha256=O2dngLsmUUnpMtW1eN3OiM0rGQNBIlXSvZmym_jAsvU,904
57
+ utilities/pydantic.py,sha256=aP6OKowg2Md4rgQuQ5qTSF4bTbBuq7WtRb7zS3JSRGY,1841
58
+ utilities/pyinstrument.py,sha256=KU7_wPX63TY_8kSps0WZ0ijpN7vXQUkp175vdg-CIdE,899
59
59
  utilities/pyrsistent.py,sha256=wVOVIe_68AAaa-lUE9y-TEzDawVp1uEIc_zfoDgr5ww,2287
60
- utilities/pytest.py,sha256=KoHSwJbIY2CHtFUlUr_gnEk7z1DVTaldl8RDQ4tDkG4,7837
60
+ utilities/pytest.py,sha256=zP4CWKXpRVk4aRDRxolUAvqQwX7wgDO8lzmkQfuZaZo,7832
61
61
  utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
62
62
  utilities/python_dotenv.py,sha256=edXsvHZhZnYeqfMfrsRRpj7_9eJI6uizh3xLx8Q9B3w,3228
63
63
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
@@ -79,18 +79,19 @@ utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
79
79
  utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
80
80
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
81
81
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
82
- utilities/traceback.py,sha256=k3QhUca-643rl11S9uhibfNPlxjavOboCK56036KRcE,8859
82
+ utilities/traceback.py,sha256=l9onlqDdW5GCSIFbU_-htBE7KlsvsJdNtGrK6-k0RCQ,8759
83
83
  utilities/types.py,sha256=gP04CcCOyFrG7BgblVCsrrChiuO2x842NDVW-GF7odo,18370
84
- utilities/typing.py,sha256=H6ysJkI830aRwLsMKz0SZIw4cpcsm7d6KhQOwr-SDh0,13817
84
+ utilities/typing.py,sha256=kQWywPcRbFBKmvQBELmgbiqSHsnlo_D0ru53vl6KDeY,13846
85
85
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
86
- utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
86
+ utilities/tzlocal.py,sha256=P5BjqTiYskeCwjE7i9zycCFXO4MWdZgYCh4jut-LpzA,1042
87
87
  utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
88
88
  utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
89
89
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
90
- utilities/whenever.py,sha256=QbXgFAPuUL7PCp2hajmIP-FFIfIR1J6Y0TxJbeoj60I,18434
90
+ utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
91
+ utilities/whenever2.py,sha256=aba0RN9GX_XnRsvmZNbHwkD15NmV4l00VFZ3HTK8vT8,3511
91
92
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
92
- utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
93
- dycw_utilities-0.131.1.dist-info/METADATA,sha256=wuztGM9vYp7gFU1LsLTuS2JxrKHKBH-AAMFyvjQ6Uo4,12989
94
- dycw_utilities-0.131.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
- dycw_utilities-0.131.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
96
- dycw_utilities-0.131.1.dist-info/RECORD,,
93
+ utilities/zoneinfo.py,sha256=tvcgu3QzDxe2suTexi2QzRGpin7VK1TjHa0JYYxT69I,1862
94
+ dycw_utilities-0.131.3.dist-info/METADATA,sha256=HPiEoWeAQVRNdWO4SDBiTNNhYKhISMiSDIC4fxgJyOo,10222
95
+ dycw_utilities-0.131.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.131.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.131.3.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.131.1"
3
+ __version__ = "0.131.3"
utilities/altair.py CHANGED
@@ -23,6 +23,7 @@ from altair import (
23
23
  )
24
24
  from altair.utils.schemapi import Undefined
25
25
 
26
+ from utilities.atomicwrites import writer
26
27
  from utilities.functions import ensure_bytes, ensure_number
27
28
  from utilities.iterables import always_iterable
28
29
  from utilities.tempfile import TemporaryDirectory
@@ -265,8 +266,6 @@ def save_chart(
265
266
  chart: _ChartLike, path: PathLike, /, *, overwrite: bool = False
266
267
  ) -> None:
267
268
  """Atomically save a chart to disk."""
268
- from utilities.atomicwrites import writer
269
-
270
269
  with writer(path, overwrite=overwrite) as temp:
271
270
  chart.save(str(temp), format="png")
272
271
 
@@ -280,8 +279,6 @@ def save_charts_as_pdf(
280
279
  """Atomically save a chart/set of charts to disk."""
281
280
  from img2pdf import convert
282
281
 
283
- from utilities.atomicwrites import writer
284
-
285
282
  with TemporaryDirectory() as temp_dir:
286
283
  temp_paths = [temp_dir.joinpath(f"{i}.png") for i in range(len(charts))]
287
284
  for chart, temp_path in zip(charts, temp_paths, strict=True):
utilities/fastapi.py CHANGED
@@ -9,6 +9,8 @@ from uvicorn import Config, Server
9
9
 
10
10
  from utilities.asyncio import Looper
11
11
  from utilities.datetime import SECOND, datetime_duration_to_float
12
+ from utilities.tzlocal import get_now_local # skipif-ci
13
+ from utilities.whenever import serialize_zoned_datetime # skipif-ci
12
14
 
13
15
  if TYPE_CHECKING:
14
16
  from types import TracebackType
@@ -29,9 +31,6 @@ class _PingerReceiverApp(FastAPI):
29
31
 
30
32
  @self.get("/ping") # skipif-ci
31
33
  def ping() -> str:
32
- from utilities.tzlocal import get_now_local # skipif-ci
33
- from utilities.whenever import serialize_zoned_datetime # skipif-ci
34
-
35
34
  now = serialize_zoned_datetime(get_now_local()) # skipif-ci
36
35
  return f"pong @ {now}" # skipif-ci
37
36
 
utilities/fpdf2.py CHANGED
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, override
6
6
  from fpdf import FPDF
7
7
  from fpdf.enums import XPos, YPos
8
8
 
9
+ from utilities.tzlocal import get_now_local
10
+
9
11
  if TYPE_CHECKING:
10
12
  from collections.abc import Iterator
11
13
 
@@ -43,8 +45,6 @@ def yield_pdf(*, header: str | None = None) -> Iterator[_BasePDF]:
43
45
 
44
46
  @override
45
47
  def footer(self) -> None:
46
- from utilities.tzlocal import get_now_local
47
-
48
48
  self.set_y(-15)
49
49
  self.set_font(family="Helvetica", style="I", size=8)
50
50
  page_no, now = self.page_no(), get_now_local()
utilities/hypothesis.py CHANGED
@@ -47,6 +47,7 @@ from hypothesis.strategies import (
47
47
  uuids,
48
48
  )
49
49
  from hypothesis.utils.conventions import not_set
50
+ from whenever import Date, DateDelta, PlainDateTime
50
51
 
51
52
  from utilities.datetime import (
52
53
  DATETIME_MAX_NAIVE,
@@ -88,7 +89,7 @@ from utilities.platform import IS_WINDOWS
88
89
  from utilities.sentinel import Sentinel, sentinel
89
90
  from utilities.tempfile import TEMP_DIR, TemporaryDirectory
90
91
  from utilities.version import Version
91
- from utilities.zoneinfo import UTC
92
+ from utilities.zoneinfo import UTC, ensure_time_zone
92
93
 
93
94
  if TYPE_CHECKING:
94
95
  from collections.abc import Collection, Hashable, Iterable, Iterator, Sequence
@@ -96,9 +97,10 @@ if TYPE_CHECKING:
96
97
 
97
98
  from hypothesis.database import ExampleDatabase
98
99
  from numpy.random import RandomState
100
+ from whenever import ZonedDateTime
99
101
 
100
102
  from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
101
- from utilities.types import Duration, Number, RoundMode
103
+ from utilities.types import Duration, Number, RoundMode, TimeZoneLike
102
104
 
103
105
 
104
106
  _T = TypeVar("_T")
@@ -158,6 +160,45 @@ def bool_arrays(
158
160
  ##
159
161
 
160
162
 
163
+ @composite
164
+ def date_deltas_whenever(
165
+ draw: DrawFn,
166
+ /,
167
+ *,
168
+ min_value: MaybeSearchStrategy[DateDelta | None] = None,
169
+ max_value: MaybeSearchStrategy[DateDelta | None] = None,
170
+ ) -> DateDelta:
171
+ """Strategy for generating date deltas."""
172
+ from utilities.whenever2 import DATE_DELTA_MAX, DATE_DELTA_MIN
173
+
174
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
175
+ match min_value_:
176
+ case None:
177
+ min_value_ = DATE_DELTA_MIN
178
+ case DateDelta():
179
+ ...
180
+ case _ as never:
181
+ assert_never(never)
182
+ match max_value_:
183
+ case None:
184
+ max_value_ = DATE_DELTA_MAX
185
+ case DateDelta():
186
+ ...
187
+ case _ as never:
188
+ assert_never(never)
189
+ min_years, min_months, min_days = min_value_.in_years_months_days()
190
+ assert min_years == 0
191
+ assert min_months == 0
192
+ max_years, max_months, max_days = max_value_.in_years_months_days()
193
+ assert max_years == 0
194
+ assert max_months == 0
195
+ days = draw(integers(min_value=min_days, max_value=max_days))
196
+ return DateDelta(days=days)
197
+
198
+
199
+ ##
200
+
201
+
161
202
  @composite
162
203
  def date_durations(
163
204
  draw: DrawFn,
@@ -238,6 +279,41 @@ def dates_two_digit_year(
238
279
  ##
239
280
 
240
281
 
282
+ @composite
283
+ def dates_whenever(
284
+ draw: DrawFn,
285
+ /,
286
+ *,
287
+ min_value: MaybeSearchStrategy[Date | None] = None,
288
+ max_value: MaybeSearchStrategy[Date | None] = None,
289
+ ) -> Date:
290
+ """Strategy for generating dates."""
291
+ from utilities.whenever2 import DATE_MAX, DATE_MIN
292
+
293
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
294
+ match min_value_:
295
+ case None:
296
+ min_value_ = DATE_MIN
297
+ case Date():
298
+ ...
299
+ case _ as never:
300
+ assert_never(never)
301
+ match max_value_:
302
+ case None:
303
+ max_value_ = DATE_MAX
304
+ case Date():
305
+ ...
306
+ case _ as never:
307
+ assert_never(never)
308
+ py_date = draw(
309
+ dates(min_value=min_value_.py_date(), max_value=max_value_.py_date())
310
+ )
311
+ return Date.from_py_date(py_date)
312
+
313
+
314
+ ##
315
+
316
+
241
317
  @composite
242
318
  def datetime_durations(
243
319
  draw: DrawFn,
@@ -920,7 +996,7 @@ def _pairs_map(elements: list[_T], /) -> tuple[_T, _T]:
920
996
 
921
997
  def paths() -> SearchStrategy[Path]:
922
998
  """Strategy for generating `Path`s."""
923
- reserved = {"NUL"}
999
+ reserved = {"AUX", "NUL"}
924
1000
  strategy = text_ascii(min_size=1, max_size=10).filter(lambda x: x not in reserved)
925
1001
  return lists(strategy, max_size=10).map(lambda parts: Path(*parts))
926
1002
 
@@ -965,6 +1041,43 @@ class PlainDateTimesError(Exception):
965
1041
  ##
966
1042
 
967
1043
 
1044
+ @composite
1045
+ def plain_datetimes_whenever(
1046
+ draw: DrawFn,
1047
+ /,
1048
+ *,
1049
+ min_value: MaybeSearchStrategy[PlainDateTime | None] = None,
1050
+ max_value: MaybeSearchStrategy[PlainDateTime | None] = None,
1051
+ ) -> PlainDateTime:
1052
+ """Strategy for generating plain datetimes."""
1053
+ from utilities.whenever2 import PLAIN_DATE_TIME_MAX, PLAIN_DATE_TIME_MIN
1054
+
1055
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1056
+ match min_value_:
1057
+ case None:
1058
+ min_value_ = PLAIN_DATE_TIME_MIN
1059
+ case PlainDateTime():
1060
+ ...
1061
+ case _ as never:
1062
+ assert_never(never)
1063
+ match max_value_:
1064
+ case None:
1065
+ max_value_ = PLAIN_DATE_TIME_MAX
1066
+ case PlainDateTime():
1067
+ ...
1068
+ case _ as never:
1069
+ assert_never(never)
1070
+ py_datetime = draw(
1071
+ datetimes(
1072
+ min_value=min_value_.py_datetime(), max_value=max_value_.py_datetime()
1073
+ )
1074
+ )
1075
+ return PlainDateTime.from_py_datetime(py_datetime)
1076
+
1077
+
1078
+ ##
1079
+
1080
+
968
1081
  @composite
969
1082
  def random_states(
970
1083
  draw: DrawFn, /, *, seed: MaybeSearchStrategy[int | None] = None
@@ -1427,6 +1540,46 @@ class ZonedDateTimesError(Exception):
1427
1540
  return "Rounding requires a timedelta; got None"
1428
1541
 
1429
1542
 
1543
+ ##
1544
+
1545
+
1546
+ @composite
1547
+ def zoned_datetimes_whenever(
1548
+ draw: DrawFn,
1549
+ /,
1550
+ *,
1551
+ min_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1552
+ max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
1553
+ time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
1554
+ ) -> ZonedDateTime:
1555
+ """Strategy for generating zoned datetimes."""
1556
+ from whenever import PlainDateTime, ZonedDateTime
1557
+
1558
+ min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1559
+ time_zone_ = ensure_time_zone(draw2(draw, time_zone))
1560
+ match min_value_:
1561
+ case None | PlainDateTime():
1562
+ ...
1563
+ case ZonedDateTime():
1564
+ with assume_does_not_raise(ValueError):
1565
+ min_value_ = min_value_.to_tz(time_zone_.key).to_plain()
1566
+ case _ as never:
1567
+ assert_never(never)
1568
+ match max_value_:
1569
+ case None | PlainDateTime():
1570
+ ...
1571
+ case ZonedDateTime():
1572
+ with assume_does_not_raise(ValueError):
1573
+ max_value_ = max_value_.to_tz(time_zone_.key).to_plain()
1574
+ case _ as never:
1575
+ assert_never(never)
1576
+ plain_datetime = draw(
1577
+ plain_datetimes_whenever(min_value=min_value_, max_value=max_value_)
1578
+ )
1579
+ with assume_does_not_raise(ValueError):
1580
+ return plain_datetime.assume_tz(time_zone_.key, disambiguate="raise")
1581
+
1582
+
1430
1583
  __all__ = [
1431
1584
  "Draw2Error",
1432
1585
  "MaybeSearchStrategy",
@@ -1435,8 +1588,10 @@ __all__ = [
1435
1588
  "ZonedDateTimesError",
1436
1589
  "assume_does_not_raise",
1437
1590
  "bool_arrays",
1591
+ "date_deltas_whenever",
1438
1592
  "date_durations",
1439
1593
  "dates_two_digit_year",
1594
+ "dates_whenever",
1440
1595
  "datetime_durations",
1441
1596
  "draw2",
1442
1597
  "float32s",
@@ -1460,6 +1615,7 @@ __all__ = [
1460
1615
  "paths",
1461
1616
  "plain_datetimes",
1462
1617
  "plain_datetimes",
1618
+ "plain_datetimes_whenever",
1463
1619
  "random_states",
1464
1620
  "sentinels",
1465
1621
  "sets_fixed_length",
@@ -1480,4 +1636,5 @@ __all__ = [
1480
1636
  "uint64s",
1481
1637
  "versions",
1482
1638
  "zoned_datetimes",
1639
+ "zoned_datetimes_whenever",
1483
1640
  ]
@@ -4,6 +4,7 @@ from contextlib import asynccontextmanager
4
4
  from dataclasses import dataclass
5
5
  from typing import TYPE_CHECKING, override
6
6
 
7
+ from utilities.atomicwrites import writer # pragma: no cover
7
8
  from utilities.iterables import OneEmptyError, OneNonUniqueError, one
8
9
  from utilities.reprlib import get_repr
9
10
 
@@ -23,8 +24,6 @@ if TYPE_CHECKING:
23
24
 
24
25
  def save_chart(chart: Chart, path: PathLike, /, *, overwrite: bool = False) -> None:
25
26
  """Atomically save a chart to disk."""
26
- from utilities.atomicwrites import writer # pragma: no cover
27
-
28
27
  chart.show(block=False) # pragma: no cover
29
28
  with ( # pragma: no cover
30
29
  writer(path, overwrite=overwrite) as temp,
utilities/logging.py CHANGED
@@ -32,6 +32,7 @@ from typing import (
32
32
  override,
33
33
  )
34
34
 
35
+ from utilities.atomicwrites import move_many
35
36
  from utilities.dataclasses import replace_non_sentinel
36
37
  from utilities.datetime import (
37
38
  SECOND,
@@ -43,6 +44,7 @@ from utilities.errors import ImpossibleCaseError
43
44
  from utilities.iterables import OneEmptyError, always_iterable, one
44
45
  from utilities.pathlib import ensure_suffix, get_path
45
46
  from utilities.sentinel import Sentinel, sentinel
47
+ from utilities.tzlocal import get_now_local
46
48
 
47
49
  if TYPE_CHECKING:
48
50
  from collections.abc import Callable, Iterable, Mapping
@@ -190,7 +192,7 @@ def get_formatter(
190
192
  ) -> Formatter:
191
193
  """Get the formatter; colored if available."""
192
194
  if whenever:
193
- from utilities.whenever import WheneverLogRecord
195
+ from utilities.whenever2 import WheneverLogRecord
194
196
 
195
197
  setLogRecordFactory(WheneverLogRecord)
196
198
  format_ = format_.replace("{asctime}", "{zoned_datetime}")
@@ -426,8 +428,6 @@ def _compute_rollover_actions(
426
428
  patterns: _RolloverPatterns | None = None,
427
429
  backup_count: int = 1,
428
430
  ) -> _RolloverActions:
429
- from utilities.tzlocal import get_now_local
430
-
431
431
  patterns = (
432
432
  _compute_rollover_patterns(stem, suffix) if patterns is None else patterns
433
433
  )
@@ -470,8 +470,6 @@ class _RolloverActions:
470
470
  rotations: set[_Rotation] = field(default_factory=set)
471
471
 
472
472
  def do(self) -> None:
473
- from utilities.atomicwrites import move_many
474
-
475
473
  for deletion in self.deletions:
476
474
  deletion.delete()
477
475
  move_many(*((r.file.path, r.destination) for r in self.rotations))
utilities/orjson.py CHANGED
@@ -742,8 +742,6 @@ class OrjsonFormatter(Formatter):
742
742
 
743
743
  @override
744
744
  def format(self, record: LogRecord) -> str:
745
- from utilities.tzlocal import get_local_time_zone
746
-
747
745
  extra = {
748
746
  k: v
749
747
  for k, v in record.__dict__.items()
utilities/pickle.py CHANGED
@@ -4,6 +4,8 @@ import gzip
4
4
  from pickle import dump, load
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
+ from utilities.atomicwrites import writer
8
+
7
9
  if TYPE_CHECKING:
8
10
  from utilities.types import PathLike
9
11
 
@@ -16,8 +18,6 @@ def read_pickle(path: PathLike, /) -> Any:
16
18
 
17
19
  def write_pickle(obj: Any, path: PathLike, /, *, overwrite: bool = False) -> None:
18
20
  """Write an object to disk."""
19
- from utilities.atomicwrites import writer
20
-
21
21
  with writer(path, overwrite=overwrite) as temp, gzip.open(temp, mode="wb") as gz:
22
22
  dump(obj, gz)
23
23
 
utilities/pydantic.py CHANGED
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, TypeVar, override
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
+ from utilities.atomicwrites import writer
10
+
9
11
  if TYPE_CHECKING:
10
12
  from utilities.types import PathLike
11
13
 
@@ -52,8 +54,6 @@ class _LoadModelIsADirectoryError(LoadModelError):
52
54
 
53
55
 
54
56
  def save_model(model: BaseModel, path: PathLike, /, *, overwrite: bool = False) -> None:
55
- from utilities.atomicwrites import writer
56
-
57
57
  with writer(path, overwrite=overwrite) as temp, temp.open(mode="w") as fh:
58
58
  _ = fh.write(model.model_dump_json())
59
59
 
utilities/pyinstrument.py CHANGED
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  from pyinstrument.profiler import Profiler
8
8
 
9
+ from utilities.atomicwrites import writer
9
10
  from utilities.datetime import serialize_compact
10
11
  from utilities.pathlib import get_path
11
12
  from utilities.tzlocal import get_now_local
@@ -19,8 +20,6 @@ if TYPE_CHECKING:
19
20
  @contextmanager
20
21
  def profile(*, path: MaybeCallablePathLike | None = Path.cwd) -> Iterator[None]:
21
22
  """Profile the contents of a block."""
22
- from utilities.atomicwrites import writer
23
-
24
23
  with Profiler() as profiler:
25
24
  yield
26
25
  filename = get_path(path=path).joinpath(
utilities/pytest.py CHANGED
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, ParamSpec, assert_never, cast, override
9
9
 
10
10
  from pytest import fixture
11
11
 
12
+ from utilities.atomicwrites import writer
12
13
  from utilities.datetime import datetime_duration_to_float, get_now
13
14
  from utilities.functools import cache
14
15
  from utilities.hashlib import md5_hash
@@ -248,8 +249,6 @@ def _throttle_md5_hash(text: str, /) -> str:
248
249
 
249
250
 
250
251
  def _throttle_write(path: Path, now: float, /) -> None:
251
- from utilities.atomicwrites import writer
252
-
253
252
  with writer(path, overwrite=True) as temp, temp.open(mode="w") as fh:
254
253
  _ = fh.write(str(now))
255
254
 
utilities/traceback.py CHANGED
@@ -13,6 +13,7 @@ from socket import gethostname
13
13
  from traceback import TracebackException
14
14
  from typing import TYPE_CHECKING, override
15
15
 
16
+ from utilities.atomicwrites import writer
16
17
  from utilities.datetime import get_datetime, get_now, serialize_compact
17
18
  from utilities.errors import repr_error
18
19
  from utilities.iterables import OneEmptyError, one
@@ -26,8 +27,9 @@ from utilities.reprlib import (
26
27
  RICH_MAX_WIDTH,
27
28
  yield_mapping_repr,
28
29
  )
30
+ from utilities.tzlocal import get_local_time_zone, get_now_local
29
31
  from utilities.version import get_version
30
- from utilities.whenever import serialize_duration
32
+ from utilities.whenever import serialize_duration, serialize_zoned_datetime
31
33
 
32
34
  if TYPE_CHECKING:
33
35
  from collections.abc import Callable, Iterator, Sequence
@@ -84,9 +86,6 @@ def _yield_header_lines(
84
86
  version: MaybeCallableVersionLike | None = None,
85
87
  ) -> Iterator[str]:
86
88
  """Yield the header lines."""
87
- from utilities.tzlocal import get_local_time_zone, get_now_local
88
- from utilities.whenever import serialize_zoned_datetime
89
-
90
89
  now = get_now_local()
91
90
  start_use = get_datetime(datetime=start)
92
91
  start_use = (
@@ -247,9 +246,6 @@ def _make_except_hook_inner(
247
246
  slim = format_exception_stack(exc_val, header=True, start=start, version=version)
248
247
  _ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
249
248
  if path is not None:
250
- from utilities.atomicwrites import writer
251
- from utilities.tzlocal import get_now_local
252
-
253
249
  path = (
254
250
  get_path(path=path)
255
251
  .joinpath(serialize_compact(get_now_local()))
utilities/typing.py CHANGED
@@ -234,7 +234,7 @@ def is_instance_gen(obj: Any, type_: Any, /) -> bool:
234
234
  """Check if an instance relationship holds, except bool<int."""
235
235
  # parent
236
236
  if isinstance(type_, tuple):
237
- return any(is_instance_gen(obj, t) for t in type_)
237
+ return any(is_instance_gen(obj, t) for t in type_) # skipif-ci-and-not-windows
238
238
  if is_literal_type(type_):
239
239
  return obj in get_args(type_)
240
240
  if is_union_type(type_):
utilities/tzlocal.py CHANGED
@@ -20,9 +20,12 @@ def get_local_time_zone() -> ZoneInfo:
20
20
  return time_zone
21
21
 
22
22
 
23
+ LOCAL_TIME_ZONE = get_local_time_zone()
24
+
25
+
23
26
  def get_now_local() -> dt.datetime:
24
27
  """Get the current local time."""
25
- return dt.datetime.now(tz=get_local_time_zone())
28
+ return dt.datetime.now(tz=LOCAL_TIME_ZONE)
26
29
 
27
30
 
28
31
  NOW_LOCAL = get_now_local()
@@ -37,6 +40,8 @@ TODAY_LOCAL = get_today_local()
37
40
 
38
41
 
39
42
  __all__ = [
43
+ "LOCAL_TIME_ZONE",
44
+ "LOCAL_TIME_ZONE",
40
45
  "NOW_LOCAL",
41
46
  "TODAY_LOCAL",
42
47
  "get_local_time_zone",
utilities/whenever.py CHANGED
@@ -4,9 +4,7 @@ import datetime as dt
4
4
  import re
5
5
  from contextlib import suppress
6
6
  from dataclasses import dataclass
7
- from functools import cache
8
- from logging import LogRecord
9
- from typing import TYPE_CHECKING, Any, override
7
+ from typing import TYPE_CHECKING, override
10
8
 
11
9
  from whenever import (
12
10
  Date,
@@ -35,8 +33,6 @@ from utilities.re import (
35
33
  from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
36
34
 
37
35
  if TYPE_CHECKING:
38
- from zoneinfo import ZoneInfo
39
-
40
36
  from utilities.types import (
41
37
  DateLike,
42
38
  DateTimeLike,
@@ -565,64 +561,6 @@ class SerializeZonedDateTimeError(Exception):
565
561
  ##
566
562
 
567
563
 
568
- class WheneverLogRecord(LogRecord):
569
- """Log record powered by `whenever`."""
570
-
571
- zoned_datetime: str
572
-
573
- @override
574
- def __init__(
575
- self,
576
- name: str,
577
- level: int,
578
- pathname: str,
579
- lineno: int,
580
- msg: object,
581
- args: Any,
582
- exc_info: Any,
583
- func: str | None = None,
584
- sinfo: str | None = None,
585
- ) -> None:
586
- super().__init__(
587
- name, level, pathname, lineno, msg, args, exc_info, func, sinfo
588
- )
589
- length = self._get_length()
590
- plain = format(self._get_now().to_plain().format_common_iso(), f"{length}s")
591
- time_zone = self._get_time_zone_key()
592
- self.zoned_datetime = f"{plain}[{time_zone}]"
593
-
594
- @classmethod
595
- @cache
596
- def _get_time_zone(cls) -> ZoneInfo:
597
- """Get the local timezone."""
598
- try:
599
- from utilities.tzlocal import get_local_time_zone
600
- except ModuleNotFoundError: # pragma: no cover
601
- return UTC
602
- return get_local_time_zone()
603
-
604
- @classmethod
605
- @cache
606
- def _get_time_zone_key(cls) -> str:
607
- """Get the local timezone as a string."""
608
- return cls._get_time_zone().key
609
-
610
- @classmethod
611
- @cache
612
- def _get_length(cls) -> int:
613
- """Get maximum length of a formatted string."""
614
- now = cls._get_now().replace(nanosecond=1000).to_plain()
615
- return len(now.format_common_iso())
616
-
617
- @classmethod
618
- def _get_now(cls) -> ZonedDateTime:
619
- """Get the current zoned datetime."""
620
- return ZonedDateTime.now(cls._get_time_zone().key)
621
-
622
-
623
- ##
624
-
625
-
626
564
  def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
627
565
  """Serialize a timedelta."""
628
566
  total_microseconds = datetime_duration_to_microseconds(timedelta)
@@ -672,7 +610,6 @@ __all__ = [
672
610
  "SerializePlainDateTimeError",
673
611
  "SerializeTimeDeltaError",
674
612
  "SerializeZonedDateTimeError",
675
- "WheneverLogRecord",
676
613
  "check_valid_zoned_datetime",
677
614
  "ensure_date",
678
615
  "ensure_datetime",
utilities/whenever2.py ADDED
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime as dt
4
+ from functools import cache
5
+ from logging import LogRecord
6
+ from typing import TYPE_CHECKING, Any, override
7
+
8
+ from whenever import Date, DateTimeDelta, PlainDateTime, ZonedDateTime
9
+
10
+ from utilities.tzlocal import LOCAL_TIME_ZONE
11
+ from utilities.zoneinfo import UTC, get_time_zone_name
12
+
13
+ if TYPE_CHECKING:
14
+ from utilities.types import TimeZoneLike
15
+
16
+
17
+ DATE_MIN = Date.from_py_date(dt.date.min)
18
+ DATE_MAX = Date.from_py_date(dt.date.max)
19
+ PLAIN_DATE_TIME_MIN = PlainDateTime.from_py_datetime(dt.datetime.min) # noqa: DTZ901
20
+ PLAIN_DATE_TIME_MAX = PlainDateTime.from_py_datetime(dt.datetime.max) # noqa: DTZ901
21
+ ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
22
+ ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
23
+ DATE_TIME_DELTA_MIN = DateTimeDelta(days=-3652059, seconds=-316192377600)
24
+ DATE_TIME_DELTA_MAX = DateTimeDelta(days=3652059, seconds=316192377600)
25
+ DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
26
+ DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
27
+ TIME_DELTA_MIN = DATE_TIME_DELTA_MIN.time_part()
28
+ TIME_DELTA_MAX = DATE_TIME_DELTA_MAX.time_part()
29
+
30
+
31
+ ##
32
+
33
+
34
+ def from_timestamp(i: float, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
35
+ """Get a zoned datetime from a timestamp."""
36
+ return ZonedDateTime.from_timestamp(i, tz=get_time_zone_name(time_zone))
37
+
38
+
39
+ def from_timestamp_millis(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
40
+ """Get a zoned datetime from a timestamp (in milliseconds)."""
41
+ return ZonedDateTime.from_timestamp_millis(i, tz=get_time_zone_name(time_zone))
42
+
43
+
44
+ def from_timestamp_nanos(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
45
+ """Get a zoned datetime from a timestamp (in nanoseconds)."""
46
+ return ZonedDateTime.from_timestamp_nanos(i, tz=get_time_zone_name(time_zone))
47
+
48
+
49
+ ##
50
+
51
+
52
+ def get_now(*, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
53
+ """Get the current zoned datetime."""
54
+ return ZonedDateTime.now(get_time_zone_name(time_zone))
55
+
56
+
57
+ NOW_UTC = get_now(time_zone=UTC)
58
+
59
+
60
+ def get_now_local() -> ZonedDateTime:
61
+ """Get the current local time."""
62
+ return get_now(time_zone="local")
63
+
64
+
65
+ ##
66
+
67
+
68
+ class WheneverLogRecord(LogRecord):
69
+ """Log record powered by `whenever`."""
70
+
71
+ zoned_datetime: str
72
+
73
+ @override
74
+ def __init__(
75
+ self,
76
+ name: str,
77
+ level: int,
78
+ pathname: str,
79
+ lineno: int,
80
+ msg: object,
81
+ args: Any,
82
+ exc_info: Any,
83
+ func: str | None = None,
84
+ sinfo: str | None = None,
85
+ ) -> None:
86
+ super().__init__(
87
+ name, level, pathname, lineno, msg, args, exc_info, func, sinfo
88
+ )
89
+ length = self._get_length()
90
+ plain = format(get_now_local().to_plain().format_common_iso(), f"{length}s")
91
+ self.zoned_datetime = f"{plain}[{LOCAL_TIME_ZONE.key}]"
92
+
93
+ @classmethod
94
+ @cache
95
+ def _get_length(cls) -> int:
96
+ """Get maximum length of a formatted string."""
97
+ now = get_now_local().replace(nanosecond=1000).to_plain()
98
+ return len(now.format_common_iso())
99
+
100
+
101
+ __all__ = [
102
+ "DATE_DELTA_MAX",
103
+ "DATE_DELTA_MIN",
104
+ "DATE_MAX",
105
+ "DATE_MIN",
106
+ "DATE_TIME_DELTA_MAX",
107
+ "DATE_TIME_DELTA_MIN",
108
+ "PLAIN_DATE_TIME_MAX",
109
+ "PLAIN_DATE_TIME_MIN",
110
+ "TIME_DELTA_MAX",
111
+ "TIME_DELTA_MIN",
112
+ "ZONED_DATE_TIME_MAX",
113
+ "ZONED_DATE_TIME_MIN",
114
+ "WheneverLogRecord",
115
+ "from_timestamp",
116
+ "from_timestamp_millis",
117
+ "from_timestamp_nanos",
118
+ "get_now",
119
+ "get_now",
120
+ "get_now_local",
121
+ ]
utilities/zoneinfo.py CHANGED
@@ -5,6 +5,8 @@ from dataclasses import dataclass
5
5
  from typing import TYPE_CHECKING, assert_never, cast, override
6
6
  from zoneinfo import ZoneInfo
7
7
 
8
+ from utilities.tzlocal import get_local_time_zone
9
+
8
10
  if TYPE_CHECKING:
9
11
  from utilities.types import TimeZone, TimeZoneLike
10
12
 
@@ -21,8 +23,6 @@ def ensure_time_zone(obj: TimeZoneLike, /) -> ZoneInfo:
21
23
  case ZoneInfo() as zone_info:
22
24
  return zone_info
23
25
  case "local":
24
- from utilities.tzlocal import get_local_time_zone
25
-
26
26
  return get_local_time_zone()
27
27
  case str() as key:
28
28
  return ZoneInfo(key)