dycw-utilities 0.146.2__py3-none-any.whl → 0.178.1__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.178.1.dist-info/METADATA +34 -0
- dycw_utilities-0.178.1.dist-info/RECORD +105 -0
- dycw_utilities-0.178.1.dist-info/WHEEL +4 -0
- {dycw_utilities-0.146.2.dist-info → dycw_utilities-0.178.1.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +10 -7
- utilities/asyncio.py +129 -50
- 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 +387 -0
- utilities/enum.py +2 -2
- utilities/errors.py +17 -3
- 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 +33 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +131 -93
- 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/packaging.py +115 -0
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +298 -0
- utilities/platform.py +5 -4
- utilities/polars.py +934 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +317 -153
- utilities/pottery.py +10 -86
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +4 -51
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +100 -126
- 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 +9 -4
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +1977 -0
- utilities/tempfile.py +112 -4
- utilities/testbook.py +50 -0
- utilities/text.py +174 -42
- utilities/throttle.py +158 -0
- utilities/timer.py +2 -2
- utilities/traceback.py +59 -38
- utilities/types.py +68 -22
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +663 -178
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.146.2.dist-info/METADATA +0 -41
- dycw_utilities-0.146.2.dist-info/RECORD +0 -99
- dycw_utilities-0.146.2.dist-info/WHEEL +0 -4
- dycw_utilities-0.146.2.dist-info/licenses/LICENSE +0 -21
- utilities/aiolimiter.py +0 -25
- utilities/eventkit.py +0 -388
- utilities/period.py +0 -237
- utilities/python_dotenv.py +0 -101
- utilities/streamlit.py +0 -105
- utilities/typed_settings.py +0 -144
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dycw-utilities
|
|
3
|
+
Version: 0.178.1
|
|
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,<2
|
|
8
|
+
Requires-Dist: typing-extensions>=4.15.0,<5
|
|
9
|
+
Requires-Dist: tzlocal>=5.3.1,<6
|
|
10
|
+
Requires-Dist: whenever>=0.9.4,<1
|
|
11
|
+
Requires-Dist: coloredlogs>=15.0.1,<16 ; extra == 'logging'
|
|
12
|
+
Requires-Dist: dycw-pytest-only>=2.1.1,<3 ; extra == 'test'
|
|
13
|
+
Requires-Dist: hypothesis>=6.150.0,<7 ; extra == 'test'
|
|
14
|
+
Requires-Dist: pytest>=9.0.2,<10 ; extra == 'test'
|
|
15
|
+
Requires-Dist: pytest-asyncio>=1.3.0,<2 ; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-cov>=7.0.0,<8 ; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-instafail>=0.5.0,<1 ; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest-lazy-fixtures>=1.4.0,<2 ; extra == 'test'
|
|
19
|
+
Requires-Dist: pytest-randomly>=4.0.1,<5 ; extra == 'test'
|
|
20
|
+
Requires-Dist: pytest-regressions>=2.8.3,<3 ; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest-repeat>=0.9.4,<1 ; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest-rerunfailures>=16.1,<17 ; extra == 'test'
|
|
23
|
+
Requires-Dist: pytest-rng>=1.0.0,<2 ; extra == 'test'
|
|
24
|
+
Requires-Dist: pytest-timeout>=2.4.0,<3 ; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest-xdist>=3.8.0,<4 ; extra == 'test'
|
|
26
|
+
Requires-Dist: testbook>=0.4.2,<1 ; 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,105 @@
|
|
|
1
|
+
utilities/__init__.py,sha256=UPRGb4cG5FkpFHiF05BtsrvJN4xEBqAOklkZld8UbEc,60
|
|
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=_cUgyU3207VH8e6IdQ88GNssvOVSkvEsUx95LiaayjY,10635
|
|
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/packaging.py,sha256=z_3v2ofEkqIMLhiEvP8zvQUaUFIqY6d-9XK_ZfsZTb0,3723
|
|
48
|
+
utilities/parse.py,sha256=g7Qm9eBOIeDId2tGA021CIaeF6jp1TI8rx4srdvlyoo,17937
|
|
49
|
+
utilities/pathlib.py,sha256=N4Ip8R9eCM-6GfvxUJ3T9oQIle2C2P52F-13BCFRdTg,9345
|
|
50
|
+
utilities/permissions.py,sha256=vLXlWztSVYffbrxptne7ksj6dU1HLekm4fEvS4ny_4Q,8944
|
|
51
|
+
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
|
52
|
+
utilities/platform.py,sha256=R3ldt2-DlI7la9ng6Rxt1CThd2lL0Ai2tC0TbabtCC0,2800
|
|
53
|
+
utilities/polars.py,sha256=JPzN4UqQDC7R4IXsIuXEIXRiwHSrkiSZcD8UOfwGPuE,87535
|
|
54
|
+
utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
|
|
55
|
+
utilities/postgres.py,sha256=g3tEwTI8TdmiCbRME61ffQ0xaibdpXPu8mJOOHvjPKc,12532
|
|
56
|
+
utilities/pottery.py,sha256=nA0SsF9irvfC0tk68YAr08tuL9lGRSlBKihSx7Ibk84,3963
|
|
57
|
+
utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
|
|
58
|
+
utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
|
|
59
|
+
utilities/pwd.py,sha256=pzlLkBfSTZsCVkMrAxEmuEoNmm-6goklfAygVmOEZqs,707
|
|
60
|
+
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
|
+
utilities/pydantic.py,sha256=z_PnYXN_UNNLQwo_XF7cr4GDcf5wicvscD6aFYVOEvA,238
|
|
62
|
+
utilities/pydantic_settings.py,sha256=53tQTpHFtA6UIuzHKzauZtW_bSRwL5lQnNwTWeO4Fjw,7608
|
|
63
|
+
utilities/pydantic_settings_sops.py,sha256=9Ou6Cx6PiYOU49vtkKqqW1Sdp_i3WlVyg8KkUUKNliM,2310
|
|
64
|
+
utilities/pyinstrument.py,sha256=hnXaL-4HE7wWBI5JKaPfYTpsrXe68YiuZKahHV0VJn8,841
|
|
65
|
+
utilities/pytest.py,sha256=p8fjpiBuMUbaH0DnpM0j5BxnPLtH3uiMEkBKAjpM3Ss,6481
|
|
66
|
+
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
67
|
+
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
|
68
|
+
utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
|
|
69
|
+
utilities/pytest_regressions.py,sha256=tJxW38u-zpoyjW1N4zogBx4V_07r-ibDInddcEUyXmc,4763
|
|
70
|
+
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
|
71
|
+
utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
|
|
72
|
+
utilities/redis.py,sha256=gybjqKea33Jy50n4dHTS14JdquqHaJqHF2dixQljYWQ,30172
|
|
73
|
+
utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
|
|
74
|
+
utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
|
|
75
|
+
utilities/sentinel.py,sha256=A_p5jX2K0Yc5XBfoYHyBLqHsEWzE1ByOdDuzzA2pZnE,1434
|
|
76
|
+
utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
|
|
77
|
+
utilities/shutil.py,sha256=knJ7hx42FtIfGByxQZMcOSgQCDlaSony325g505ps3A,480
|
|
78
|
+
utilities/slack_sdk.py,sha256=76-DYtcGiUhEvl-voMamc5OjfF7Y7nCq54Bys1arqzw,2233
|
|
79
|
+
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
|
80
|
+
utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
|
|
81
|
+
utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
|
|
82
|
+
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
83
|
+
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
|
84
|
+
utilities/subprocess.py,sha256=eGRRQF-OXDy2kMSy7dv4fUzcPizvRgGBxYvPh-QVqZo,53980
|
|
85
|
+
utilities/tempfile.py,sha256=4kRGd4hyINDX4hpcYMtwzDcd-4IKjpTWT0MnjnKD4hE,4221
|
|
86
|
+
utilities/testbook.py,sha256=dy9ID7GoFitMTCi4XBDTE4_mJm9uLTPyGgGrkJ1yuec,1304
|
|
87
|
+
utilities/text.py,sha256=UKW6xtF4bb0i-Gu1jctJvvQmeXk6U6UYNqoEnCLdaOo,14102
|
|
88
|
+
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
89
|
+
utilities/throttle.py,sha256=zVyqSdFGIAUQUj4dk-Zh8SFiuX0DUmRWMsn-qwSYk3g,4911
|
|
90
|
+
utilities/timer.py,sha256=BGlwEVznx67scuLOUohyWJ4d5rTnwtk-IR4yLXFiNfo,2574
|
|
91
|
+
utilities/traceback.py,sha256=B_sc0TRUv-mGDnF-ek05nbqjmBiHr3-wvxliAqIF5hI,9608
|
|
92
|
+
utilities/types.py,sha256=UwxYajRnupKTCnzReaYWYqtKdpIPeSO-d97Wu4bLEq8,18878
|
|
93
|
+
utilities/typing.py,sha256=xuR8LxzjD-XlSftTM3TNvGVdQyV1mzpdwWdDMzWwCPE,25310
|
|
94
|
+
utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
|
|
95
|
+
utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
|
96
|
+
utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
|
|
97
|
+
utilities/version.py,sha256=ipBj5-WYY_nelp2uwFlApfWWCzTLzPwpovUi9x_OBMs,5085
|
|
98
|
+
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
99
|
+
utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
|
|
100
|
+
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
|
101
|
+
utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
|
|
102
|
+
dycw_utilities-0.178.1.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
|
|
103
|
+
dycw_utilities-0.178.1.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
104
|
+
dycw_utilities-0.178.1.dist-info/METADATA,sha256=Brf1aK3mbaluoqDK93fH9FVQ09bBfP6JDIx85DCRa0M,1398
|
|
105
|
+
dycw_utilities-0.178.1.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,20 +31,26 @@ 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.
|
|
39
|
+
from utilities.functions import ensure_int, ensure_not_none
|
|
40
|
+
from utilities.os import is_pytest
|
|
40
41
|
from utilities.random import SYSTEM_RANDOM
|
|
42
|
+
from utilities.reprlib import get_repr
|
|
41
43
|
from utilities.sentinel import Sentinel, sentinel
|
|
44
|
+
from utilities.shelve import yield_shelf
|
|
45
|
+
from utilities.text import to_bool
|
|
46
|
+
from utilities.warnings import suppress_warnings
|
|
42
47
|
from utilities.whenever import get_now, round_date_or_date_time, to_nanoseconds
|
|
43
48
|
|
|
44
49
|
if TYPE_CHECKING:
|
|
45
50
|
from asyncio import _CoroutineLike
|
|
46
51
|
from asyncio.subprocess import Process
|
|
47
52
|
from collections.abc import (
|
|
53
|
+
AsyncIterable,
|
|
48
54
|
AsyncIterator,
|
|
49
55
|
Callable,
|
|
50
56
|
ItemsView,
|
|
@@ -56,15 +62,18 @@ if TYPE_CHECKING:
|
|
|
56
62
|
)
|
|
57
63
|
from contextvars import Context
|
|
58
64
|
from random import Random
|
|
65
|
+
from shelve import Shelf
|
|
59
66
|
from types import TracebackType
|
|
60
67
|
|
|
61
68
|
from whenever import ZonedDateTime
|
|
62
69
|
|
|
70
|
+
from utilities.shelve import _Flag
|
|
63
71
|
from utilities.types import (
|
|
64
72
|
Coro,
|
|
65
73
|
Delta,
|
|
66
|
-
|
|
74
|
+
MaybeCallableBoolLike,
|
|
67
75
|
MaybeType,
|
|
76
|
+
PathLike,
|
|
68
77
|
SupportsKeysAndGetItem,
|
|
69
78
|
)
|
|
70
79
|
|
|
@@ -222,7 +231,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
222
231
|
_semaphore: Semaphore | None
|
|
223
232
|
_timeout: Delta | None
|
|
224
233
|
_error: MaybeType[BaseException]
|
|
225
|
-
_debug:
|
|
234
|
+
_debug: MaybeCallableBoolLike
|
|
226
235
|
_stack: AsyncExitStack
|
|
227
236
|
_timeout_cm: _AsyncGeneratorContextManager[None] | None
|
|
228
237
|
|
|
@@ -233,7 +242,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
233
242
|
max_tasks: int | None = None,
|
|
234
243
|
timeout: Delta | None = None,
|
|
235
244
|
error: MaybeType[BaseException] = TimeoutError,
|
|
236
|
-
debug:
|
|
245
|
+
debug: MaybeCallableBoolLike = False,
|
|
237
246
|
) -> None:
|
|
238
247
|
super().__init__()
|
|
239
248
|
self._max_tasks = max_tasks
|
|
@@ -266,7 +275,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
266
275
|
_ = await super().__aexit__(et, exc, tb)
|
|
267
276
|
case False:
|
|
268
277
|
_ = await super().__aexit__(et, exc, tb)
|
|
269
|
-
case
|
|
278
|
+
case never:
|
|
270
279
|
assert_never(never)
|
|
271
280
|
|
|
272
281
|
@override
|
|
@@ -303,7 +312,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
303
312
|
self.create_task(make_coro(*args, **kwargs))
|
|
304
313
|
for _ in range(self._max_tasks)
|
|
305
314
|
]
|
|
306
|
-
case
|
|
315
|
+
case never:
|
|
307
316
|
assert_never(never)
|
|
308
317
|
|
|
309
318
|
async def run_or_create_task[T](
|
|
@@ -318,11 +327,11 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
318
327
|
return await coro
|
|
319
328
|
case False:
|
|
320
329
|
return self.create_task(coro, name=name, context=context)
|
|
321
|
-
case
|
|
330
|
+
case never:
|
|
322
331
|
assert_never(never)
|
|
323
332
|
|
|
324
333
|
def _is_debug(self) -> bool:
|
|
325
|
-
return to_bool(
|
|
334
|
+
return to_bool(self._debug) or (
|
|
326
335
|
(self._max_tasks is not None) and (self._max_tasks <= 0)
|
|
327
336
|
)
|
|
328
337
|
|
|
@@ -340,13 +349,43 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
340
349
|
##
|
|
341
350
|
|
|
342
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
|
+
|
|
370
|
+
def get_coroutine_name(func: Callable[[], Coro[Any]], /) -> str:
|
|
371
|
+
"""Get the name of a coroutine, and then dispose of it gracefully."""
|
|
372
|
+
coro = func()
|
|
373
|
+
name = coro.__name__
|
|
374
|
+
with suppress_warnings(
|
|
375
|
+
message="coroutine '.*' was never awaited", category=RuntimeWarning
|
|
376
|
+
):
|
|
377
|
+
del coro
|
|
378
|
+
return name
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
##
|
|
382
|
+
|
|
383
|
+
|
|
343
384
|
async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> list[T]:
|
|
344
385
|
"""Get items from a queue; if empty then wait."""
|
|
345
386
|
try:
|
|
346
387
|
items = [await queue.get()]
|
|
347
388
|
except RuntimeError as error: # pragma: no cover
|
|
348
|
-
from utilities.pytest import is_pytest
|
|
349
|
-
|
|
350
389
|
if (not is_pytest()) or (error.args[0] != "Event loop is closed"):
|
|
351
390
|
raise
|
|
352
391
|
return []
|
|
@@ -376,27 +415,38 @@ def get_items_nowait[T](queue: Queue[T], /, *, max_size: int | None = None) -> l
|
|
|
376
415
|
##
|
|
377
416
|
|
|
378
417
|
|
|
379
|
-
async def
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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"
|
|
400
450
|
|
|
401
451
|
|
|
402
452
|
##
|
|
@@ -463,27 +513,21 @@ class StreamCommandOutput:
|
|
|
463
513
|
|
|
464
514
|
@property
|
|
465
515
|
def return_code(self) -> int:
|
|
466
|
-
return ensure_int(self.process.returncode)
|
|
516
|
+
return ensure_int(self.process.returncode)
|
|
467
517
|
|
|
468
518
|
|
|
469
519
|
async def stream_command(cmd: str, /) -> StreamCommandOutput:
|
|
470
520
|
"""Run a shell command asynchronously and stream its output in real time."""
|
|
471
|
-
process = await create_subprocess_shell(
|
|
472
|
-
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
)
|
|
477
|
-
proc_stderr = ensure_not_none( # skipif-not-windows
|
|
478
|
-
process.stderr, desc="process.stderr"
|
|
479
|
-
)
|
|
480
|
-
ret_stdout = StringIO() # skipif-not-windows
|
|
481
|
-
ret_stderr = StringIO() # skipif-not-windows
|
|
482
|
-
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:
|
|
483
527
|
_ = tg.create_task(_stream_one(proc_stdout, stdout, ret_stdout))
|
|
484
528
|
_ = tg.create_task(_stream_one(proc_stderr, stderr, ret_stderr))
|
|
485
|
-
_ = await process.wait()
|
|
486
|
-
return StreamCommandOutput(
|
|
529
|
+
_ = await process.wait()
|
|
530
|
+
return StreamCommandOutput(
|
|
487
531
|
process=process, stdout=ret_stdout.getvalue(), stderr=ret_stderr.getvalue()
|
|
488
532
|
)
|
|
489
533
|
|
|
@@ -492,7 +536,7 @@ async def _stream_one(
|
|
|
492
536
|
input_: StreamReader, out_stream: TextIO, ret_stream: StringIO, /
|
|
493
537
|
) -> None:
|
|
494
538
|
"""Asynchronously read from a stream and write to the target output stream."""
|
|
495
|
-
while True:
|
|
539
|
+
while True:
|
|
496
540
|
line = await input_.readline()
|
|
497
541
|
if not line:
|
|
498
542
|
break
|
|
@@ -518,13 +562,47 @@ async def timeout_td(
|
|
|
518
562
|
raise error from None
|
|
519
563
|
|
|
520
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
|
+
|
|
521
594
|
__all__ = [
|
|
522
595
|
"AsyncDict",
|
|
523
596
|
"EnhancedTaskGroup",
|
|
597
|
+
"OneAsyncEmptyError",
|
|
598
|
+
"OneAsyncError",
|
|
599
|
+
"OneAsyncNonUniqueError",
|
|
524
600
|
"StreamCommandOutput",
|
|
601
|
+
"chain_async",
|
|
602
|
+
"get_coroutine_name",
|
|
525
603
|
"get_items",
|
|
526
604
|
"get_items_nowait",
|
|
527
|
-
"
|
|
605
|
+
"one_async",
|
|
528
606
|
"put_items",
|
|
529
607
|
"put_items_nowait",
|
|
530
608
|
"sleep_max",
|
|
@@ -533,4 +611,5 @@ __all__ = [
|
|
|
533
611
|
"sleep_until",
|
|
534
612
|
"stream_command",
|
|
535
613
|
"timeout_td",
|
|
614
|
+
"yield_locked_shelf",
|
|
536
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
|
|