dycw-utilities 0.148.5__py3-none-any.whl → 0.175.31__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.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.175.31.dist-info/METADATA +34 -0
- dycw_utilities-0.175.31.dist-info/RECORD +103 -0
- dycw_utilities-0.175.31.dist-info/WHEEL +4 -0
- {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.175.31.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +10 -7
- utilities/asyncio.py +113 -64
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +144 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +381 -0
- utilities/enum.py +2 -2
- utilities/errors.py +1 -1
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +361 -79
- utilities/importlib.py +17 -1
- utilities/inflect.py +1 -1
- utilities/iterables.py +12 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +74 -85
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +298 -0
- utilities/platform.py +4 -4
- utilities/polars.py +934 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +296 -174
- utilities/pottery.py +8 -73
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +155 -46
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +27 -8
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +8 -3
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +1947 -0
- utilities/tempfile.py +95 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +2 -2
- utilities/traceback.py +46 -36
- utilities/types.py +62 -23
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +661 -151
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.148.5.dist-info/METADATA +0 -41
- dycw_utilities-0.148.5.dist-info/RECORD +0 -95
- dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
- dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
- utilities/eventkit.py +0 -388
- utilities/period.py +0 -237
- utilities/typed_settings.py +0 -144
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dycw-utilities
|
|
3
|
+
Version: 0.175.31
|
|
4
|
+
Summary: Miscellaneous Python utilities
|
|
5
|
+
Author: Derek Wan
|
|
6
|
+
Author-email: Derek Wan <d.wan@icloud.com>
|
|
7
|
+
Requires-Dist: atomicwrites>=1.4.1,<1.5
|
|
8
|
+
Requires-Dist: typing-extensions>=4.15.0,<4.16
|
|
9
|
+
Requires-Dist: tzlocal>=5.3.1,<5.4
|
|
10
|
+
Requires-Dist: whenever>=0.9.4,<0.10
|
|
11
|
+
Requires-Dist: coloredlogs>=15.0.1,<15.1 ; extra == 'logging'
|
|
12
|
+
Requires-Dist: dycw-pytest-only>=2.1.1,<2.2 ; extra == 'test'
|
|
13
|
+
Requires-Dist: hypothesis>=6.148.8,<6.149 ; extra == 'test'
|
|
14
|
+
Requires-Dist: pytest>=9.0.2,<9.1 ; extra == 'test'
|
|
15
|
+
Requires-Dist: pytest-asyncio>=1.3.0,<1.4 ; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-cov>=7.0.0,<7.1 ; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-instafail>=0.5.0,<0.6 ; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest-lazy-fixtures>=1.4.0,<1.5 ; extra == 'test'
|
|
19
|
+
Requires-Dist: pytest-randomly>=4.0.1,<4.1 ; extra == 'test'
|
|
20
|
+
Requires-Dist: pytest-regressions>=2.8.3,<2.9 ; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest-repeat>=0.9.4,<0.10 ; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest-rerunfailures>=16.1,<16.2 ; extra == 'test'
|
|
23
|
+
Requires-Dist: pytest-rng>=1.0.0,<1.1 ; extra == 'test'
|
|
24
|
+
Requires-Dist: pytest-timeout>=2.4.0,<2.5 ; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest-xdist>=3.8.0,<3.9 ; extra == 'test'
|
|
26
|
+
Requires-Dist: testbook>=0.4.2,<0.5 ; extra == 'test'
|
|
27
|
+
Requires-Python: >=3.12
|
|
28
|
+
Provides-Extra: logging
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# `python-utilities`
|
|
33
|
+
|
|
34
|
+
Miscellaneous Python utilities
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
utilities/__init__.py,sha256=y-0IecU_rQ9B_0aVstJ6gMlcyWn2-Hw_v-RcSXW8pWY,61
|
|
2
|
+
utilities/altair.py,sha256=TLfRFbG9HwG7SLXoJ-v0r-t49ZaGgTQZD82cpjVi4vs,9085
|
|
3
|
+
utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
|
|
4
|
+
utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
|
|
5
|
+
utilities/atools.py,sha256=6neeCcgXxK2dlsc0xp15Za7nSucbCgFtAJepGI_-WXU,2549
|
|
6
|
+
utilities/cachetools.py,sha256=2S9LMHIunDYMIu8JGI7OLN04sQ7-xZGdEdP1Li0vksA,2775
|
|
7
|
+
utilities/click.py,sha256=ScLzBLoBp8Si5YjgB18A0IVMAR-r4sGUnVfJbAaru98,19191
|
|
8
|
+
utilities/concurrent.py,sha256=fHeW2SZ_TEMfFY0C8pyQI6aPlnecvx9x6SuUwBWj_JY,2853
|
|
9
|
+
utilities/contextlib.py,sha256=iP7R2tIm6ZsbfLD5ks6UKBYwj50e9gBI8AkpLN-chro,7476
|
|
10
|
+
utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
|
|
11
|
+
utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
|
|
12
|
+
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
13
|
+
utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
|
|
14
|
+
utilities/docker.py,sha256=IOOdxAqfJfy1SXwy3e4VaAR9ag42W0HTcNcQz7DJPF8,10501
|
|
15
|
+
utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
|
|
16
|
+
utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
|
|
17
|
+
utilities/fastapi.py,sha256=TqyKvBjiMS594sXPjrz-KRTLMb3l3D3rZ1zAYV7GfOk,1454
|
|
18
|
+
utilities/fpdf2.py,sha256=dSiYz0FJTD2sQuxpxqFWwwIe2-p6Y7oTB9Tv0Jajit0,1866
|
|
19
|
+
utilities/functions.py,sha256=18Zda7nTloARdcEudH8YJ4e13xAdWShAGhPNN4w2Gyc,21498
|
|
20
|
+
utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
|
|
21
|
+
utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
22
|
+
utilities/git.py,sha256=U1RFvCTANGENgx9wVBDvllioqBQZM2ns12ivKhOsaO4,414
|
|
23
|
+
utilities/grp.py,sha256=1vV3gNR9dQsl1vtUtvC_2qgVdQzm7O8lLMSh56cTbeg,694
|
|
24
|
+
utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
|
|
25
|
+
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
|
26
|
+
utilities/http.py,sha256=TsavEfHlRtlLaeV21Z6KZh0qbPw-kvD1zsQdZ7Kep5Q,977
|
|
27
|
+
utilities/hypothesis.py,sha256=NUu30pl5kjL3tzo-m8SMRwTqLAmTWK-_Sau2NemJcQo,46773
|
|
28
|
+
utilities/importlib.py,sha256=SkVVtIjVC7bjJ36doXnmnmFiYe5tLbip4YAfYJj8Ycg,892
|
|
29
|
+
utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
|
|
30
|
+
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
|
31
|
+
utilities/iterables.py,sha256=t2TsW-K3rVlS6y4_tqcc1fk9RwJV-bi7G_VwduMABK0,42558
|
|
32
|
+
utilities/jinja2.py,sha256=JpNHMcyMJDguX8rBN4wEz-v4En7w6WHXvYJr4Xw-F0o,4691
|
|
33
|
+
utilities/json.py,sha256=-WcGtSsCr9Y42wHZzAMnfvU6ihAfVftylFfRUORaDFo,2102
|
|
34
|
+
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
|
35
|
+
utilities/libcst.py,sha256=ngD4wxnR3Kh-RBVmU5l5ST7cuZLhMZwyMDjHZe5mhTs,5581
|
|
36
|
+
utilities/lightweight_charts.py,sha256=YM3ojBvJxuCSUBu_KrhFBmaMCvRPvupKC3qkm-UVZq4,2751
|
|
37
|
+
utilities/logging.py,sha256=mckcX_0Q5njVgnhtIoePcDHjYjTVELw7ExutiiDnzsU,18724
|
|
38
|
+
utilities/math.py,sha256=cevB-YyEYAzJTWtkAr7qeeu-hbxorDI3gMznXlmNQkw,26897
|
|
39
|
+
utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
|
|
40
|
+
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
|
41
|
+
utilities/more_itertools.py,sha256=syfIPhQF_WS-YiicdGe2h5F1G-Ld12Q2XsVduL2hA40,10908
|
|
42
|
+
utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
|
|
43
|
+
utilities/operator.py,sha256=C3NylZWGTVWRpwYHOPVhaLgRhw0DfpS4_XQ8KfPhBLQ,3613
|
|
44
|
+
utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
|
45
|
+
utilities/orjson.py,sha256=T_0SlK811ysg46d3orvIPY3JpBa4FRMpP2wlPQo7-gU,41854
|
|
46
|
+
utilities/os.py,sha256=kjKKSQfnRqFTTZ315iavaaGd3gGuYNoSWlxVLCJjyQs,4852
|
|
47
|
+
utilities/parse.py,sha256=g7Qm9eBOIeDId2tGA021CIaeF6jp1TI8rx4srdvlyoo,17937
|
|
48
|
+
utilities/pathlib.py,sha256=N4Ip8R9eCM-6GfvxUJ3T9oQIle2C2P52F-13BCFRdTg,9345
|
|
49
|
+
utilities/permissions.py,sha256=vLXlWztSVYffbrxptne7ksj6dU1HLekm4fEvS4ny_4Q,8944
|
|
50
|
+
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
|
51
|
+
utilities/platform.py,sha256=R3ldt2-DlI7la9ng6Rxt1CThd2lL0Ai2tC0TbabtCC0,2800
|
|
52
|
+
utilities/polars.py,sha256=JPzN4UqQDC7R4IXsIuXEIXRiwHSrkiSZcD8UOfwGPuE,87535
|
|
53
|
+
utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
|
|
54
|
+
utilities/postgres.py,sha256=g3tEwTI8TdmiCbRME61ffQ0xaibdpXPu8mJOOHvjPKc,12532
|
|
55
|
+
utilities/pottery.py,sha256=nA0SsF9irvfC0tk68YAr08tuL9lGRSlBKihSx7Ibk84,3963
|
|
56
|
+
utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
|
|
57
|
+
utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
|
|
58
|
+
utilities/pwd.py,sha256=pzlLkBfSTZsCVkMrAxEmuEoNmm-6goklfAygVmOEZqs,707
|
|
59
|
+
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
+
utilities/pydantic.py,sha256=z_PnYXN_UNNLQwo_XF7cr4GDcf5wicvscD6aFYVOEvA,238
|
|
61
|
+
utilities/pydantic_settings.py,sha256=53tQTpHFtA6UIuzHKzauZtW_bSRwL5lQnNwTWeO4Fjw,7608
|
|
62
|
+
utilities/pydantic_settings_sops.py,sha256=9Ou6Cx6PiYOU49vtkKqqW1Sdp_i3WlVyg8KkUUKNliM,2310
|
|
63
|
+
utilities/pyinstrument.py,sha256=hnXaL-4HE7wWBI5JKaPfYTpsrXe68YiuZKahHV0VJn8,841
|
|
64
|
+
utilities/pytest.py,sha256=9HHwYgZQe6CRF0ekHQEFH05gmoP4Ne0V54RrtUNDfi4,10524
|
|
65
|
+
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
66
|
+
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
|
67
|
+
utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
|
|
68
|
+
utilities/pytest_regressions.py,sha256=tJxW38u-zpoyjW1N4zogBx4V_07r-ibDInddcEUyXmc,4763
|
|
69
|
+
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
|
70
|
+
utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
|
|
71
|
+
utilities/redis.py,sha256=gybjqKea33Jy50n4dHTS14JdquqHaJqHF2dixQljYWQ,30172
|
|
72
|
+
utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
|
|
73
|
+
utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
|
|
74
|
+
utilities/sentinel.py,sha256=A_p5jX2K0Yc5XBfoYHyBLqHsEWzE1ByOdDuzzA2pZnE,1434
|
|
75
|
+
utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
|
|
76
|
+
utilities/shutil.py,sha256=knJ7hx42FtIfGByxQZMcOSgQCDlaSony325g505ps3A,480
|
|
77
|
+
utilities/slack_sdk.py,sha256=76-DYtcGiUhEvl-voMamc5OjfF7Y7nCq54Bys1arqzw,2233
|
|
78
|
+
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
|
79
|
+
utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
|
|
80
|
+
utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
|
|
81
|
+
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
82
|
+
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
|
83
|
+
utilities/subprocess.py,sha256=ei0OD73S0WX7ZqHCQp9UB3hY9OWbAUVIrLoz9niIfQQ,53031
|
|
84
|
+
utilities/tempfile.py,sha256=a3_M1QyxGZql_VcGkBOQBeWbbkItjgkfIpVyzU1UAic,3843
|
|
85
|
+
utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
|
|
86
|
+
utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
|
|
87
|
+
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
88
|
+
utilities/timer.py,sha256=BGlwEVznx67scuLOUohyWJ4d5rTnwtk-IR4yLXFiNfo,2574
|
|
89
|
+
utilities/traceback.py,sha256=B_sc0TRUv-mGDnF-ek05nbqjmBiHr3-wvxliAqIF5hI,9608
|
|
90
|
+
utilities/types.py,sha256=UwxYajRnupKTCnzReaYWYqtKdpIPeSO-d97Wu4bLEq8,18878
|
|
91
|
+
utilities/typing.py,sha256=xuR8LxzjD-XlSftTM3TNvGVdQyV1mzpdwWdDMzWwCPE,25310
|
|
92
|
+
utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
|
|
93
|
+
utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
|
94
|
+
utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
|
|
95
|
+
utilities/version.py,sha256=ipBj5-WYY_nelp2uwFlApfWWCzTLzPwpovUi9x_OBMs,5085
|
|
96
|
+
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
97
|
+
utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
|
|
98
|
+
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
|
99
|
+
utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
|
|
100
|
+
dycw_utilities-0.175.31.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
101
|
+
dycw_utilities-0.175.31.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
102
|
+
dycw_utilities-0.175.31.dist-info/METADATA,sha256=WX68a6sQoOamTeIZsrXvDdI_VKItf-Jm3Cam7p1jR_U,1443
|
|
103
|
+
dycw_utilities-0.175.31.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/altair.py
CHANGED
|
@@ -29,8 +29,6 @@ from utilities.iterables import always_iterable
|
|
|
29
29
|
from utilities.tempfile import TemporaryDirectory
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import Sequence
|
|
33
|
-
|
|
34
32
|
from polars import DataFrame
|
|
35
33
|
|
|
36
34
|
from utilities.types import PathLike
|
|
@@ -43,7 +41,7 @@ _WIDTH = 800
|
|
|
43
41
|
|
|
44
42
|
@dataclass(kw_only=True, slots=True)
|
|
45
43
|
class _PlotDataFramesSpec:
|
|
46
|
-
y:
|
|
44
|
+
y: list[str]
|
|
47
45
|
height: int = _HEIGHT
|
|
48
46
|
|
|
49
47
|
|
|
@@ -96,7 +94,8 @@ def plot_dataframes(
|
|
|
96
94
|
# lines
|
|
97
95
|
selection = selection_point(bind="legend", fields=[var_name], nearest=False)
|
|
98
96
|
lines = [
|
|
99
|
-
chart
|
|
97
|
+
chart
|
|
98
|
+
.mark_line(interpolate=interpolate)
|
|
100
99
|
.encode(
|
|
101
100
|
x=x_use,
|
|
102
101
|
y=Y(value_name).scale(zero=False),
|
|
@@ -126,7 +125,8 @@ def plot_dataframes(
|
|
|
126
125
|
else:
|
|
127
126
|
tooltip_format_use = Undefined
|
|
128
127
|
rules = [
|
|
129
|
-
chart
|
|
128
|
+
chart
|
|
129
|
+
.transform_pivot(var_name, value=value_name, groupby=[x_use])
|
|
130
130
|
.mark_rule(color="gray")
|
|
131
131
|
.encode(
|
|
132
132
|
x=x_use,
|
|
@@ -145,7 +145,9 @@ def plot_dataframes(
|
|
|
145
145
|
]
|
|
146
146
|
zoom = selection_interval(bind="scales", encodings=["x"])
|
|
147
147
|
chart = (
|
|
148
|
-
|
|
148
|
+
vconcat_charts(*layers)
|
|
149
|
+
.add_params(zoom)
|
|
150
|
+
.resolve_scale(color="independent", x="shared")
|
|
149
151
|
)
|
|
150
152
|
if title is not None:
|
|
151
153
|
chart = chart.properties(title=title)
|
|
@@ -229,7 +231,8 @@ def plot_intraday_dataframe(
|
|
|
229
231
|
)
|
|
230
232
|
|
|
231
233
|
data4 = (
|
|
232
|
-
data3
|
|
234
|
+
data3
|
|
235
|
+
.group_by("_date_index")
|
|
233
236
|
.agg(
|
|
234
237
|
col(f"_{datetime}_index").min().alias(f"{datetime}_index_min"),
|
|
235
238
|
(col(f"_{datetime}_index").max() + 1).alias(f"{datetime}_index_max"),
|
utilities/asyncio.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import sys
|
|
5
4
|
from asyncio import (
|
|
6
5
|
Lock,
|
|
7
6
|
Queue,
|
|
@@ -22,6 +21,7 @@ from contextlib import (
|
|
|
22
21
|
)
|
|
23
22
|
from dataclasses import dataclass
|
|
24
23
|
from io import StringIO
|
|
24
|
+
from pathlib import Path
|
|
25
25
|
from subprocess import PIPE
|
|
26
26
|
from sys import stderr, stdout
|
|
27
27
|
from typing import (
|
|
@@ -31,15 +31,18 @@ from typing import (
|
|
|
31
31
|
Self,
|
|
32
32
|
TextIO,
|
|
33
33
|
assert_never,
|
|
34
|
+
cast,
|
|
34
35
|
overload,
|
|
35
36
|
override,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
from utilities.
|
|
39
|
-
from utilities.
|
|
40
|
-
from utilities.logging import get_logger
|
|
39
|
+
from utilities.functions import ensure_int, ensure_not_none
|
|
40
|
+
from utilities.os import is_pytest
|
|
41
41
|
from utilities.random import SYSTEM_RANDOM
|
|
42
|
+
from utilities.reprlib import get_repr
|
|
42
43
|
from utilities.sentinel import Sentinel, sentinel
|
|
44
|
+
from utilities.shelve import yield_shelf
|
|
45
|
+
from utilities.text import to_bool
|
|
43
46
|
from utilities.warnings import suppress_warnings
|
|
44
47
|
from utilities.whenever import get_now, round_date_or_date_time, to_nanoseconds
|
|
45
48
|
|
|
@@ -47,6 +50,7 @@ if TYPE_CHECKING:
|
|
|
47
50
|
from asyncio import _CoroutineLike
|
|
48
51
|
from asyncio.subprocess import Process
|
|
49
52
|
from collections.abc import (
|
|
53
|
+
AsyncIterable,
|
|
50
54
|
AsyncIterator,
|
|
51
55
|
Callable,
|
|
52
56
|
ItemsView,
|
|
@@ -58,17 +62,18 @@ if TYPE_CHECKING:
|
|
|
58
62
|
)
|
|
59
63
|
from contextvars import Context
|
|
60
64
|
from random import Random
|
|
65
|
+
from shelve import Shelf
|
|
61
66
|
from types import TracebackType
|
|
62
67
|
|
|
63
68
|
from whenever import ZonedDateTime
|
|
64
69
|
|
|
70
|
+
from utilities.shelve import _Flag
|
|
65
71
|
from utilities.types import (
|
|
66
72
|
Coro,
|
|
67
73
|
Delta,
|
|
68
|
-
|
|
69
|
-
LoggerOrName,
|
|
70
|
-
MaybeCallableBool,
|
|
74
|
+
MaybeCallableBoolLike,
|
|
71
75
|
MaybeType,
|
|
76
|
+
PathLike,
|
|
72
77
|
SupportsKeysAndGetItem,
|
|
73
78
|
)
|
|
74
79
|
|
|
@@ -226,7 +231,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
226
231
|
_semaphore: Semaphore | None
|
|
227
232
|
_timeout: Delta | None
|
|
228
233
|
_error: MaybeType[BaseException]
|
|
229
|
-
_debug:
|
|
234
|
+
_debug: MaybeCallableBoolLike
|
|
230
235
|
_stack: AsyncExitStack
|
|
231
236
|
_timeout_cm: _AsyncGeneratorContextManager[None] | None
|
|
232
237
|
|
|
@@ -237,7 +242,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
237
242
|
max_tasks: int | None = None,
|
|
238
243
|
timeout: Delta | None = None,
|
|
239
244
|
error: MaybeType[BaseException] = TimeoutError,
|
|
240
|
-
debug:
|
|
245
|
+
debug: MaybeCallableBoolLike = False,
|
|
241
246
|
) -> None:
|
|
242
247
|
super().__init__()
|
|
243
248
|
self._max_tasks = max_tasks
|
|
@@ -270,7 +275,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
270
275
|
_ = await super().__aexit__(et, exc, tb)
|
|
271
276
|
case False:
|
|
272
277
|
_ = await super().__aexit__(et, exc, tb)
|
|
273
|
-
case
|
|
278
|
+
case never:
|
|
274
279
|
assert_never(never)
|
|
275
280
|
|
|
276
281
|
@override
|
|
@@ -307,7 +312,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
307
312
|
self.create_task(make_coro(*args, **kwargs))
|
|
308
313
|
for _ in range(self._max_tasks)
|
|
309
314
|
]
|
|
310
|
-
case
|
|
315
|
+
case never:
|
|
311
316
|
assert_never(never)
|
|
312
317
|
|
|
313
318
|
async def run_or_create_task[T](
|
|
@@ -322,11 +327,11 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
322
327
|
return await coro
|
|
323
328
|
case False:
|
|
324
329
|
return self.create_task(coro, name=name, context=context)
|
|
325
|
-
case
|
|
330
|
+
case never:
|
|
326
331
|
assert_never(never)
|
|
327
332
|
|
|
328
333
|
def _is_debug(self) -> bool:
|
|
329
|
-
return to_bool(
|
|
334
|
+
return to_bool(self._debug) or (
|
|
330
335
|
(self._max_tasks is not None) and (self._max_tasks <= 0)
|
|
331
336
|
)
|
|
332
337
|
|
|
@@ -344,6 +349,24 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
344
349
|
##
|
|
345
350
|
|
|
346
351
|
|
|
352
|
+
def chain_async[T](*iterables: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[T]:
|
|
353
|
+
"""Asynchronous version of `chain`."""
|
|
354
|
+
|
|
355
|
+
async def iterator() -> AsyncIterator[T]:
|
|
356
|
+
for it in iterables:
|
|
357
|
+
try:
|
|
358
|
+
async for item in cast("AsyncIterable[T]", it):
|
|
359
|
+
yield item
|
|
360
|
+
except TypeError:
|
|
361
|
+
for item in cast("Iterable[T]", it):
|
|
362
|
+
yield item
|
|
363
|
+
|
|
364
|
+
return iterator()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
##
|
|
368
|
+
|
|
369
|
+
|
|
347
370
|
def get_coroutine_name(func: Callable[[], Coro[Any]], /) -> str:
|
|
348
371
|
"""Get the name of a coroutine, and then dispose of it gracefully."""
|
|
349
372
|
coro = func()
|
|
@@ -363,8 +386,6 @@ async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> li
|
|
|
363
386
|
try:
|
|
364
387
|
items = [await queue.get()]
|
|
365
388
|
except RuntimeError as error: # pragma: no cover
|
|
366
|
-
from utilities.pytest import is_pytest
|
|
367
|
-
|
|
368
389
|
if (not is_pytest()) or (error.args[0] != "Event loop is closed"):
|
|
369
390
|
raise
|
|
370
391
|
return []
|
|
@@ -394,38 +415,38 @@ def get_items_nowait[T](queue: Queue[T], /, *, max_size: int | None = None) -> l
|
|
|
394
415
|
##
|
|
395
416
|
|
|
396
417
|
|
|
397
|
-
async def
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
418
|
+
async def one_async[T](*iterables: Iterable[T] | AsyncIterable[T]) -> T:
|
|
419
|
+
"""Asynchronous version of `one`."""
|
|
420
|
+
result: T | Sentinel = sentinel
|
|
421
|
+
async for item in chain_async(*iterables):
|
|
422
|
+
if not isinstance(result, Sentinel):
|
|
423
|
+
raise OneAsyncNonUniqueError(iterables=iterables, first=result, second=item)
|
|
424
|
+
result = item
|
|
425
|
+
if isinstance(result, Sentinel):
|
|
426
|
+
raise OneAsyncEmptyError(iterables=iterables)
|
|
427
|
+
return result
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@dataclass(kw_only=True, slots=True)
|
|
431
|
+
class OneAsyncError[T](Exception):
|
|
432
|
+
iterables: tuple[Iterable[T] | AsyncIterable[T], ...]
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@dataclass(kw_only=True, slots=True)
|
|
436
|
+
class OneAsyncEmptyError[T](OneAsyncError[T]):
|
|
437
|
+
@override
|
|
438
|
+
def __str__(self) -> str:
|
|
439
|
+
return f"Iterable(s) {get_repr(self.iterables)} must not be empty"
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@dataclass(kw_only=True, slots=True)
|
|
443
|
+
class OneAsyncNonUniqueError[T](OneAsyncError):
|
|
444
|
+
first: T
|
|
445
|
+
second: T
|
|
446
|
+
|
|
447
|
+
@override
|
|
448
|
+
def __str__(self) -> str:
|
|
449
|
+
return f"Iterable(s) {get_repr(self.iterables)} must contain exactly one item; got {self.first}, {self.second} and perhaps more"
|
|
429
450
|
|
|
430
451
|
|
|
431
452
|
##
|
|
@@ -492,27 +513,21 @@ class StreamCommandOutput:
|
|
|
492
513
|
|
|
493
514
|
@property
|
|
494
515
|
def return_code(self) -> int:
|
|
495
|
-
return ensure_int(self.process.returncode)
|
|
516
|
+
return ensure_int(self.process.returncode)
|
|
496
517
|
|
|
497
518
|
|
|
498
519
|
async def stream_command(cmd: str, /) -> StreamCommandOutput:
|
|
499
520
|
"""Run a shell command asynchronously and stream its output in real time."""
|
|
500
|
-
process = await create_subprocess_shell(
|
|
501
|
-
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
)
|
|
506
|
-
proc_stderr = ensure_not_none( # skipif-not-windows
|
|
507
|
-
process.stderr, desc="process.stderr"
|
|
508
|
-
)
|
|
509
|
-
ret_stdout = StringIO() # skipif-not-windows
|
|
510
|
-
ret_stderr = StringIO() # skipif-not-windows
|
|
511
|
-
async with TaskGroup() as tg: # skipif-not-windows
|
|
521
|
+
process = await create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE)
|
|
522
|
+
proc_stdout = ensure_not_none(process.stdout, desc="process.stdout")
|
|
523
|
+
proc_stderr = ensure_not_none(process.stderr, desc="process.stderr")
|
|
524
|
+
ret_stdout = StringIO()
|
|
525
|
+
ret_stderr = StringIO()
|
|
526
|
+
async with TaskGroup() as tg:
|
|
512
527
|
_ = tg.create_task(_stream_one(proc_stdout, stdout, ret_stdout))
|
|
513
528
|
_ = tg.create_task(_stream_one(proc_stderr, stderr, ret_stderr))
|
|
514
|
-
_ = await process.wait()
|
|
515
|
-
return StreamCommandOutput(
|
|
529
|
+
_ = await process.wait()
|
|
530
|
+
return StreamCommandOutput(
|
|
516
531
|
process=process, stdout=ret_stdout.getvalue(), stderr=ret_stderr.getvalue()
|
|
517
532
|
)
|
|
518
533
|
|
|
@@ -521,7 +536,7 @@ async def _stream_one(
|
|
|
521
536
|
input_: StreamReader, out_stream: TextIO, ret_stream: StringIO, /
|
|
522
537
|
) -> None:
|
|
523
538
|
"""Asynchronously read from a stream and write to the target output stream."""
|
|
524
|
-
while True:
|
|
539
|
+
while True:
|
|
525
540
|
line = await input_.readline()
|
|
526
541
|
if not line:
|
|
527
542
|
break
|
|
@@ -547,14 +562,47 @@ async def timeout_td(
|
|
|
547
562
|
raise error from None
|
|
548
563
|
|
|
549
564
|
|
|
565
|
+
##
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
_LOCKS: AsyncDict[Path, Lock] = AsyncDict()
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@asynccontextmanager
|
|
572
|
+
async def yield_locked_shelf(
|
|
573
|
+
path: PathLike,
|
|
574
|
+
/,
|
|
575
|
+
*,
|
|
576
|
+
flag: _Flag = "c",
|
|
577
|
+
protocol: int | None = None,
|
|
578
|
+
writeback: bool = False,
|
|
579
|
+
) -> AsyncIterator[Shelf[Any]]:
|
|
580
|
+
"""Yield a shelf, behind a lock."""
|
|
581
|
+
path = Path(path)
|
|
582
|
+
try:
|
|
583
|
+
lock = _LOCKS[path]
|
|
584
|
+
except KeyError:
|
|
585
|
+
lock = Lock()
|
|
586
|
+
await _LOCKS.set(path, lock)
|
|
587
|
+
async with lock:
|
|
588
|
+
with yield_shelf(
|
|
589
|
+
path, flag=flag, protocol=protocol, writeback=writeback
|
|
590
|
+
) as shelf:
|
|
591
|
+
yield shelf
|
|
592
|
+
|
|
593
|
+
|
|
550
594
|
__all__ = [
|
|
551
595
|
"AsyncDict",
|
|
552
596
|
"EnhancedTaskGroup",
|
|
597
|
+
"OneAsyncEmptyError",
|
|
598
|
+
"OneAsyncError",
|
|
599
|
+
"OneAsyncNonUniqueError",
|
|
553
600
|
"StreamCommandOutput",
|
|
601
|
+
"chain_async",
|
|
554
602
|
"get_coroutine_name",
|
|
555
603
|
"get_items",
|
|
556
604
|
"get_items_nowait",
|
|
557
|
-
"
|
|
605
|
+
"one_async",
|
|
558
606
|
"put_items",
|
|
559
607
|
"put_items_nowait",
|
|
560
608
|
"sleep_max",
|
|
@@ -563,4 +611,5 @@ __all__ = [
|
|
|
563
611
|
"sleep_until",
|
|
564
612
|
"stream_command",
|
|
565
613
|
"timeout_td",
|
|
614
|
+
"yield_locked_shelf",
|
|
566
615
|
]
|
utilities/atomicwrites.py
CHANGED
utilities/atools.py
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
-
from
|
|
4
|
+
from functools import partial
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast, overload
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
import atools
|
|
9
|
+
from whenever import TimeDelta
|
|
7
10
|
|
|
8
|
-
from utilities.types import Coro
|
|
11
|
+
from utilities.types import Coro, PathLike
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
|
-
from
|
|
14
|
+
from atools._memoize_decorator import Keygen, Pickler
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
type _Key[**P, T] = tuple[Callable[P, Coro[T]], TimeDelta]
|
|
@@ -36,4 +39,61 @@ async def call_memoized[**P, T](
|
|
|
36
39
|
return await memoized_func(*args, **kwargs)
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
##
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def memoize[F: Callable[..., Coro[Any]]](
|
|
47
|
+
func: F,
|
|
48
|
+
/,
|
|
49
|
+
*,
|
|
50
|
+
db_path: PathLike | None = None,
|
|
51
|
+
duration: float | TimeDelta | None = None,
|
|
52
|
+
keygen: Keygen | None = None,
|
|
53
|
+
pickler: Pickler | None = None,
|
|
54
|
+
size: int | None = None,
|
|
55
|
+
) -> F: ...
|
|
56
|
+
@overload
|
|
57
|
+
def memoize[F: Callable[..., Coro[Any]]](
|
|
58
|
+
func: None = None,
|
|
59
|
+
/,
|
|
60
|
+
*,
|
|
61
|
+
db_path: PathLike | None = None,
|
|
62
|
+
duration: float | TimeDelta | None = None,
|
|
63
|
+
keygen: Keygen | None = None,
|
|
64
|
+
pickler: Pickler | None = None,
|
|
65
|
+
size: int | None = None,
|
|
66
|
+
) -> Callable[[F], F]: ...
|
|
67
|
+
def memoize[F: Callable[..., Coro[Any]]](
|
|
68
|
+
func: F | None = None,
|
|
69
|
+
/,
|
|
70
|
+
*,
|
|
71
|
+
db_path: PathLike | None = None,
|
|
72
|
+
duration: float | TimeDelta | None = None,
|
|
73
|
+
keygen: Keygen | None = None,
|
|
74
|
+
pickler: Pickler | None = None,
|
|
75
|
+
size: int | None = None,
|
|
76
|
+
) -> F | Callable[[F], F]:
|
|
77
|
+
"""Memoize a function."""
|
|
78
|
+
if func is None:
|
|
79
|
+
result = partial(
|
|
80
|
+
memoize,
|
|
81
|
+
db_path=db_path,
|
|
82
|
+
duration=duration,
|
|
83
|
+
keygen=keygen,
|
|
84
|
+
pickler=pickler,
|
|
85
|
+
size=size,
|
|
86
|
+
)
|
|
87
|
+
return cast("Callable[[F], F]", result)
|
|
88
|
+
return atools.memoize(
|
|
89
|
+
db_path=None if db_path is None else Path(db_path),
|
|
90
|
+
duration=duration.py_timedelta()
|
|
91
|
+
if isinstance(duration, TimeDelta)
|
|
92
|
+
else duration,
|
|
93
|
+
keygen=keygen,
|
|
94
|
+
pickler=pickler,
|
|
95
|
+
size=size,
|
|
96
|
+
)(func)
|
|
97
|
+
|
|
98
|
+
|
|
39
99
|
__all__ = ["call_memoized"]
|
utilities/cachetools.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Callable, Hashable, Iterable, Iterator, MutableSet
|
|
4
4
|
from math import inf
|
|
5
5
|
from time import monotonic
|
|
6
|
-
from typing import TYPE_CHECKING, Any, override
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast, override
|
|
7
7
|
|
|
8
8
|
import cachetools
|
|
9
9
|
from cachetools.func import ttl_cache
|
|
@@ -100,11 +100,14 @@ def cache[F: Callable](
|
|
|
100
100
|
typed_: bool = False,
|
|
101
101
|
) -> Callable[[F], F]:
|
|
102
102
|
"""Decorate a function with `max_size` and/or `ttl` settings."""
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
return cast(
|
|
104
|
+
"F",
|
|
105
|
+
ttl_cache(
|
|
106
|
+
maxsize=max_size,
|
|
107
|
+
ttl=inf if max_duration is None else max_duration.in_seconds(),
|
|
108
|
+
timer=timer,
|
|
109
|
+
typed=typed_,
|
|
110
|
+
),
|
|
108
111
|
)
|
|
109
112
|
|
|
110
113
|
|