dycw-utilities 0.175.17__py3-none-any.whl → 0.185.8__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.
- dycw_utilities-0.185.8.dist-info/METADATA +33 -0
- dycw_utilities-0.185.8.dist-info/RECORD +90 -0
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
- utilities/__init__.py +1 -1
- utilities/altair.py +8 -6
- utilities/asyncio.py +40 -56
- utilities/atools.py +9 -11
- utilities/cachetools.py +8 -6
- utilities/click.py +4 -3
- utilities/concurrent.py +1 -1
- utilities/constants.py +492 -0
- utilities/contextlib.py +23 -30
- utilities/contextvars.py +1 -23
- utilities/core.py +2581 -0
- utilities/dataclasses.py +16 -119
- utilities/docker.py +139 -45
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +33 -264
- utilities/http.py +2 -3
- utilities/hypothesis.py +48 -25
- utilities/iterables.py +39 -575
- utilities/jinja2.py +3 -6
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +17 -15
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +24 -25
- utilities/os.py +4 -185
- utilities/packaging.py +129 -0
- utilities/parse.py +33 -13
- utilities/pathlib.py +2 -136
- utilities/platform.py +8 -90
- utilities/polars.py +34 -31
- utilities/postgres.py +9 -4
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +18 -4
- utilities/pydantic_settings.py +7 -9
- utilities/pydantic_settings_sops.py +3 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +49 -108
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +8 -6
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +42 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +1166 -148
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -115
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +29 -12
- utilities/traceback.py +15 -22
- utilities/types.py +38 -3
- utilities/typing.py +18 -12
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +22 -150
- dycw_utilities-0.175.17.dist-info/METADATA +0 -34
- dycw_utilities-0.175.17.dist-info/RECORD +0 -103
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/grp.py +0 -28
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/permissions.py +0 -298
- utilities/pickle.py +0 -25
- utilities/pwd.py +0 -28
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -136
- utilities/tzdata.py +0 -11
- utilities/tzlocal.py +0 -28
- utilities/warnings.py +0 -65
- utilities/zipfile.py +0 -25
- utilities/zoneinfo.py +0 -133
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dycw-utilities
|
|
3
|
+
Version: 0.185.8
|
|
4
|
+
Summary: Miscellaneous Python utilities
|
|
5
|
+
Author: Derek Wan
|
|
6
|
+
Author-email: Derek Wan <d.wan@icloud.com>
|
|
7
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
8
|
+
Requires-Dist: tzdata>=2025.3
|
|
9
|
+
Requires-Dist: tzlocal>=5.3.1
|
|
10
|
+
Requires-Dist: whenever>=0.9.5
|
|
11
|
+
Requires-Dist: coloredlogs>=15.0.1 ; extra == 'logging'
|
|
12
|
+
Requires-Dist: dycw-pytest-only>=2.1.1 ; extra == 'test'
|
|
13
|
+
Requires-Dist: hypothesis>=6.150.2 ; extra == 'test'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=1.3.0 ; extra == 'test'
|
|
15
|
+
Requires-Dist: pytest-cov>=7.0.0 ; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-instafail>=0.5.0 ; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-lazy-fixtures>=1.4.0 ; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest-randomly>=4.0.1 ; extra == 'test'
|
|
19
|
+
Requires-Dist: pytest-regressions>=2.9.1 ; extra == 'test'
|
|
20
|
+
Requires-Dist: pytest-repeat>=0.9.4 ; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest-rerunfailures>=16.1 ; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest-rng>=1.0.0 ; extra == 'test'
|
|
23
|
+
Requires-Dist: pytest-timeout>=2.4.0 ; extra == 'test'
|
|
24
|
+
Requires-Dist: pytest-xdist>=3.8.0 ; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest>=9.0.2 ; extra == 'test'
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Provides-Extra: logging
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# `python-utilities`
|
|
32
|
+
|
|
33
|
+
Miscellaneous Python utilities
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
utilities/__init__.py,sha256=f2LepwDrbt4lGZTQGsx6vT40UOEvW-BOkI7_SrElAds,60
|
|
2
|
+
utilities/altair.py,sha256=248uNvy7JkhGbmgn2yj0Um2Cd2kfEDCRMvUEKTxJjto,9032
|
|
3
|
+
utilities/asyncio.py,sha256=QX_RSCUEK6gMIbrVkJnR_ZjrXUW5hkWpvSXFDyBUz1s,16294
|
|
4
|
+
utilities/atools.py,sha256=Fi2gt4ylGJrwzzMvun1lV41NsoGFm2voHcp9yili9gc,2503
|
|
5
|
+
utilities/cachetools.py,sha256=p7aedtT0QahEU5DpR7xtliIApoS0hsDSdwMKmBF74pw,2820
|
|
6
|
+
utilities/click.py,sha256=fFPqQmCaB2bwcHG62X5c0smEyjoSqmBaJ0bodeaRF9Q,19205
|
|
7
|
+
utilities/concurrent.py,sha256=nzOPz2G21VKrpttgIE9RvbK0ngssS7vbbrauFcNNM3g,2856
|
|
8
|
+
utilities/constants.py,sha256=I7xXM7bFu2gsuG_a1zRMR_T7ZfPKo9-NCczo8X7eNHE,10965
|
|
9
|
+
utilities/contextlib.py,sha256=qBDWE9g5w_6By04X90CbfWWxnuX2xi8XGeptr8BpBIA,7482
|
|
10
|
+
utilities/contextvars.py,sha256=nuSzAoco5HqdfRIVKtsdXnUxFxeBoj8XI0-I71qses4,500
|
|
11
|
+
utilities/core.py,sha256=ehfrNF-Knl3FA1Ekd9LGJ2bvQzfpRv41uxbTry5FeCE,74792
|
|
12
|
+
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
13
|
+
utilities/dataclasses.py,sha256=XwAjYxdCnDQvtB34rnq79ZuKGgybl2qXVkYZU8CN03k,28917
|
|
14
|
+
utilities/docker.py,sha256=jqE1RIHLI6lkoJoaBaFID2juJFDeTlfsm3AOVY8qXAc,10660
|
|
15
|
+
utilities/enum.py,sha256=yrQQhFQCbfSF7oLu1Bz258CHn8na3ZK5F2FjvvAztU4,5776
|
|
16
|
+
utilities/errors.py,sha256=ceCdBp4WgvbRB0EpANFeLYFEYmvDerUsepDsFOiL3Oc,1040
|
|
17
|
+
utilities/fastapi.py,sha256=GLeM8pwFYPEn8LrBcSARxvpSmiqbjJY84rN5YreQRzk,1455
|
|
18
|
+
utilities/fpdf2.py,sha256=S5vrKFv-L5LP6tImcbngYtTa7OJJ4PNToCBrEV7-Dbw,1892
|
|
19
|
+
utilities/functions.py,sha256=QYfRRoRoJv7SgxKc4OdM9trubRqLbHGSb9mNGSpg_jc,15362
|
|
20
|
+
utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
|
|
21
|
+
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
|
22
|
+
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
|
23
|
+
utilities/hypothesis.py,sha256=Er8cA3K-4KwPLjRzzm6j-HTmkn5YXB6UweF3lOnaiIE,47160
|
|
24
|
+
utilities/importlib.py,sha256=SkVVtIjVC7bjJ36doXnmnmFiYe5tLbip4YAfYJj8Ycg,892
|
|
25
|
+
utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
|
|
26
|
+
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
|
27
|
+
utilities/iterables.py,sha256=CDWPzYG_7fJHdcMZWYy1lI8-0oC6oEDdo3doBdzNjiE,27958
|
|
28
|
+
utilities/jinja2.py,sha256=nuGVbVEtvFzvcd9x1WRmN0OCDwGC_0-UOowzv3AQrqw,4567
|
|
29
|
+
utilities/jupyter.py,sha256=LQYq7oNw-eiY3xnAz-nEj6DFj0HXtq4a0n26qbDuTpE,2939
|
|
30
|
+
utilities/libcst.py,sha256=tcwn1yqwBd1O7TNRiD3Z2I7A__jem_F3CS1mmqBpAz4,5611
|
|
31
|
+
utilities/lightweight_charts.py,sha256=-rfCKBcJAfPTH6E-2podk33ag8gxLThUxWOdcE_s4KM,2660
|
|
32
|
+
utilities/logging.py,sha256=puR5c2L8XD0L19M7QxyBlvXj8a-lIaCyxt1FZ1fP54E,18789
|
|
33
|
+
utilities/math.py,sha256=-exzVZaKuOfPeMAZ24-5lVGD0tqbqjxcN1_hv8AytDY,26011
|
|
34
|
+
utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
|
|
35
|
+
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
|
36
|
+
utilities/more_itertools.py,sha256=dv2WQlKGXo34kFs1GvKjhXmRdJV3ljaKrLDC_1B83YQ,10834
|
|
37
|
+
utilities/numpy.py,sha256=i95XC2enKNC8ug1jkncLuq33Eqk3JBvXGHvROai1wIY,26148
|
|
38
|
+
utilities/operator.py,sha256=kI3VQSpH4gM68DunmC0Ir2mDiyP_BGUphcWFV1ckBm4,3601
|
|
39
|
+
utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
|
40
|
+
utilities/orjson.py,sha256=0GWskBRlvRojMpKeJYe1Mkc0gqZy-vt0qNRQ-aMbo7U,42025
|
|
41
|
+
utilities/os.py,sha256=fJ97L-YgC2swNu44GMWHn9qMSCsyT0jQKDzZZAXXTgQ,781
|
|
42
|
+
utilities/packaging.py,sha256=ws9OIfyUMx130EK-u5ZVfQI6EOdkekeCRGLcoZt2VmQ,4293
|
|
43
|
+
utilities/parse.py,sha256=BMg75FXTftmLWYqye2iMjjS-H12I16KNg3tlppJ5n98,18195
|
|
44
|
+
utilities/pathlib.py,sha256=ZnEKDiKaaEAxUQ6gnE43OUwK3DM4enYeOClChCd7qV4,6101
|
|
45
|
+
utilities/platform.py,sha256=picjLyUsWMphI6rDzpSjCX8J9lpEFz9J43o-19TM52s,900
|
|
46
|
+
utilities/polars.py,sha256=WJ-Yj25adOT2e3VpXhOUvy_h5cz8yLQajWU5slWEUH4,87400
|
|
47
|
+
utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
|
|
48
|
+
utilities/postgres.py,sha256=RQXBIaS54pObS0EFXJQAJ-Hl2H6fbilG7g20hOpt57A,12571
|
|
49
|
+
utilities/pottery.py,sha256=QJvT8I0A-7v5OdK7DSU7hzc2yKTJ9-wIhUNBkE62rlY,4015
|
|
50
|
+
utilities/pqdm.py,sha256=XcL7yZJ8TZOPOX9aQQOGhA_QdM6CI7QFOJde_R9eons,3059
|
|
51
|
+
utilities/psutil.py,sha256=yQIR1M7AtjNWaqGlAAHlAVv0OqsS6PwGBBUqCq9gohg,2054
|
|
52
|
+
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
+
utilities/pydantic.py,sha256=8wLNW3Hjor6xSJktILSRNfe4hyxSg5Xl-keWbBQ9qUk,656
|
|
54
|
+
utilities/pydantic_settings.py,sha256=UM5G5RAnfI4OApziM15Cqg2hqamNm1NnU12MclRjPX4,7564
|
|
55
|
+
utilities/pydantic_settings_sops.py,sha256=r7QhDHPfDMyexmi2krV0eIOl4Bco0MZxqyStapQyXPk,2307
|
|
56
|
+
utilities/pyinstrument.py,sha256=HtVYVNe7jJ2NuNvv4GyrDsx6lrLRa7BI7UtBWnypIwU,831
|
|
57
|
+
utilities/pytest.py,sha256=WkINIzo45omF2HDXbAFTDeMxDLs3iU2xtM1_Dcqe9TE,8339
|
|
58
|
+
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
59
|
+
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
|
60
|
+
utilities/pytest_plugins/pytest_regressions.py,sha256=p_omAAZCkiWLxydLS6LK-hIYn3GRbn-0IrcSpf36q4k,1649
|
|
61
|
+
utilities/pytest_regressions.py,sha256=T-T_2ZCDFm_nxyaEZy1zAw9-HGSAHRnPAH-TIZLTPNc,4910
|
|
62
|
+
utilities/random.py,sha256=DhBzzHoW0RS0rdfWaG0Ws9_Lw_p_doeHnlW9_7W0Pqs,4096
|
|
63
|
+
utilities/redis.py,sha256=mQuKx-cbSq5o2w-ITryiOXAv4YdHHHLrvd_qt0GMUJU,30346
|
|
64
|
+
utilities/reprlib.py,sha256=LYcIh5FbvRwL6ZPP-p1c34taQreBqDWipN5LP66yk4Y,1285
|
|
65
|
+
utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
|
|
66
|
+
utilities/shellingham.py,sha256=lV_cAZHdUK3CC_NKQZuyZSepngr7u8CZZnph3galvaE,1712
|
|
67
|
+
utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
|
|
68
|
+
utilities/shutil.py,sha256=knJ7hx42FtIfGByxQZMcOSgQCDlaSony325g505ps3A,480
|
|
69
|
+
utilities/slack_sdk.py,sha256=zBM6NpxK3iIoWL-18UVxRavBbRked3PC7mrCUuvkAoY,2297
|
|
70
|
+
utilities/sqlalchemy.py,sha256=riSz1iod_TkUDuJlhgPz-DBn8zl1kWV8G83lgd_DNL0,37300
|
|
71
|
+
utilities/sqlalchemy_polars.py,sha256=jdEb9e6MR6PBbwFThLbMM7q0MeUqoqshChyeje6ia-0,13290
|
|
72
|
+
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
73
|
+
utilities/subprocess.py,sha256=PjXK6Do5-uUMyIw1xLSa_DhvgzfmcRUHAVK6HkDqWxg,70961
|
|
74
|
+
utilities/tabulate.py,sha256=9lsjeMxb9lv2-dkHgl85Eln4Pu1RsWweylbxe5CNkAM,912
|
|
75
|
+
utilities/testbook.py,sha256=-A4hC0wNzBdT-biXSZpCI6dOgAilmSJOMArxS4Rhdag,1328
|
|
76
|
+
utilities/text.py,sha256=g4j7IkzArw1hJtSV0k8tqjYvOmvqjjYH369uYC3O9-Y,11817
|
|
77
|
+
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
78
|
+
utilities/throttle.py,sha256=fAfsMMiMm0WRL_MnY2A-_slbhlcmuXij7JwDBzR_kaw,4994
|
|
79
|
+
utilities/time.py,sha256=oVvbpGFjgavzGn5Y8CevryQJR9uithmtyBMbPI-YcC8,376
|
|
80
|
+
utilities/timer.py,sha256=a9evZ_Ob8lMz9M6kWizFedVYmHO27bmvUa4m1kAKEKk,3119
|
|
81
|
+
utilities/traceback.py,sha256=wJWAT-8PVu4xwZ5f3OHUUF3o34YwTvQw74S_wkTGvXM,9519
|
|
82
|
+
utilities/types.py,sha256=n0TDfKJTzElnh2df7QdlSIcIGPzSSS0FJyRrcIDGRLc,19539
|
|
83
|
+
utilities/typing.py,sha256=zECFvJbzmUvzg3hn3CNnhtRr_AvGYypgYdiIQRZa2gU,25366
|
|
84
|
+
utilities/uuid.py,sha256=0jVYfTrh1mIrNHi8PNvwLne_DVn7XBmT5P3g7YcBTOQ,1573
|
|
85
|
+
utilities/version.py,sha256=YBhzHz-WpfXLByZr2eEisWsOyFOOfiYYtU3fxf4uDRA,9775
|
|
86
|
+
utilities/whenever.py,sha256=VJaQINRKJwFCVd9Q4zSkG_HvezbdaGxJNcgV6A-lS1Y,57445
|
|
87
|
+
dycw_utilities-0.185.8.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
88
|
+
dycw_utilities-0.185.8.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
89
|
+
dycw_utilities-0.185.8.dist-info/METADATA,sha256=Vf6EyD1EdCdKQLTzuWcvK3SGTF9kdIkSU_0DPqrhzhU,1281
|
|
90
|
+
dycw_utilities-0.185.8.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/altair.py
CHANGED
|
@@ -23,10 +23,13 @@ from altair import (
|
|
|
23
23
|
)
|
|
24
24
|
from altair.utils.schemapi import Undefined
|
|
25
25
|
|
|
26
|
-
from utilities.
|
|
26
|
+
from utilities.core import (
|
|
27
|
+
TemporaryDirectory,
|
|
28
|
+
always_iterable,
|
|
29
|
+
write_bytes,
|
|
30
|
+
yield_write_path,
|
|
31
|
+
)
|
|
27
32
|
from utilities.functions import ensure_bytes, ensure_number
|
|
28
|
-
from utilities.iterables import always_iterable
|
|
29
|
-
from utilities.tempfile import TemporaryDirectory
|
|
30
33
|
|
|
31
34
|
if TYPE_CHECKING:
|
|
32
35
|
from polars import DataFrame
|
|
@@ -269,7 +272,7 @@ def save_chart(
|
|
|
269
272
|
chart: _ChartLike, path: PathLike, /, *, overwrite: bool = False
|
|
270
273
|
) -> None:
|
|
271
274
|
"""Atomically save a chart to disk."""
|
|
272
|
-
with
|
|
275
|
+
with yield_write_path(path, overwrite=overwrite) as temp:
|
|
273
276
|
chart.save(temp, format="png")
|
|
274
277
|
|
|
275
278
|
|
|
@@ -287,8 +290,7 @@ def save_charts_as_pdf(
|
|
|
287
290
|
for chart, temp_path in zip(charts, temp_paths, strict=True):
|
|
288
291
|
save_chart(chart, temp_path)
|
|
289
292
|
data = ensure_bytes(convert(*temp_paths))
|
|
290
|
-
|
|
291
|
-
_ = temp.write_bytes(data)
|
|
293
|
+
write_bytes(path, data, overwrite=overwrite)
|
|
292
294
|
|
|
293
295
|
|
|
294
296
|
##
|
utilities/asyncio.py
CHANGED
|
@@ -10,7 +10,6 @@ from asyncio import (
|
|
|
10
10
|
Task,
|
|
11
11
|
TaskGroup,
|
|
12
12
|
create_subprocess_shell,
|
|
13
|
-
sleep,
|
|
14
13
|
)
|
|
15
14
|
from contextlib import (
|
|
16
15
|
AbstractAsyncContextManager,
|
|
@@ -36,15 +35,12 @@ from typing import (
|
|
|
36
35
|
override,
|
|
37
36
|
)
|
|
38
37
|
|
|
39
|
-
from utilities.
|
|
40
|
-
from utilities.
|
|
41
|
-
from utilities.
|
|
42
|
-
from utilities.reprlib import get_repr
|
|
43
|
-
from utilities.sentinel import Sentinel, sentinel
|
|
38
|
+
from utilities.constants import SYSTEM_RANDOM, Sentinel, sentinel
|
|
39
|
+
from utilities.core import get_now, is_pytest, repr_
|
|
40
|
+
from utilities.functions import ensure_int, ensure_not_none, in_seconds
|
|
44
41
|
from utilities.shelve import yield_shelf
|
|
45
42
|
from utilities.text import to_bool
|
|
46
|
-
from utilities.
|
|
47
|
-
from utilities.whenever import get_now, round_date_or_date_time, to_nanoseconds
|
|
43
|
+
from utilities.whenever import round_date_or_date_time
|
|
48
44
|
|
|
49
45
|
if TYPE_CHECKING:
|
|
50
46
|
from asyncio import _CoroutineLike
|
|
@@ -69,8 +65,8 @@ if TYPE_CHECKING:
|
|
|
69
65
|
|
|
70
66
|
from utilities.shelve import _Flag
|
|
71
67
|
from utilities.types import (
|
|
72
|
-
Coro,
|
|
73
68
|
Delta,
|
|
69
|
+
Duration,
|
|
74
70
|
MaybeCallableBoolLike,
|
|
75
71
|
MaybeType,
|
|
76
72
|
PathLike,
|
|
@@ -229,7 +225,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
229
225
|
|
|
230
226
|
_max_tasks: int | None
|
|
231
227
|
_semaphore: Semaphore | None
|
|
232
|
-
_timeout:
|
|
228
|
+
_timeout: Duration | None
|
|
233
229
|
_error: MaybeType[BaseException]
|
|
234
230
|
_debug: MaybeCallableBoolLike
|
|
235
231
|
_stack: AsyncExitStack
|
|
@@ -240,7 +236,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
240
236
|
self,
|
|
241
237
|
*,
|
|
242
238
|
max_tasks: int | None = None,
|
|
243
|
-
timeout:
|
|
239
|
+
timeout: Duration | None = None,
|
|
244
240
|
error: MaybeType[BaseException] = TimeoutError,
|
|
245
241
|
debug: MaybeCallableBoolLike = False,
|
|
246
242
|
) -> None:
|
|
@@ -342,7 +338,7 @@ class EnhancedTaskGroup(TaskGroup):
|
|
|
342
338
|
return await coroutine
|
|
343
339
|
|
|
344
340
|
async def _wrap_with_timeout[T](self, coroutine: _CoroutineLike[T], /) -> T:
|
|
345
|
-
async with
|
|
341
|
+
async with timeout(self._timeout, error=self._error):
|
|
346
342
|
return await coroutine
|
|
347
343
|
|
|
348
344
|
|
|
@@ -367,20 +363,6 @@ def chain_async[T](*iterables: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[
|
|
|
367
363
|
##
|
|
368
364
|
|
|
369
365
|
|
|
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
|
-
|
|
384
366
|
async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> list[T]:
|
|
385
367
|
"""Get items from a queue; if empty then wait."""
|
|
386
368
|
try:
|
|
@@ -436,7 +418,7 @@ class OneAsyncError[T](Exception):
|
|
|
436
418
|
class OneAsyncEmptyError[T](OneAsyncError[T]):
|
|
437
419
|
@override
|
|
438
420
|
def __str__(self) -> str:
|
|
439
|
-
return f"Iterable(s) {
|
|
421
|
+
return f"Iterable(s) {repr_(self.iterables)} must not be empty"
|
|
440
422
|
|
|
441
423
|
|
|
442
424
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -446,7 +428,7 @@ class OneAsyncNonUniqueError[T](OneAsyncError):
|
|
|
446
428
|
|
|
447
429
|
@override
|
|
448
430
|
def __str__(self) -> str:
|
|
449
|
-
return f"Iterable(s) {
|
|
431
|
+
return f"Iterable(s) {repr_(self.iterables)} must contain exactly one item; got {self.first}, {self.second} and perhaps more"
|
|
450
432
|
|
|
451
433
|
|
|
452
434
|
##
|
|
@@ -467,31 +449,29 @@ def put_items_nowait[T](items: Iterable[T], queue: Queue[T], /) -> None:
|
|
|
467
449
|
##
|
|
468
450
|
|
|
469
451
|
|
|
470
|
-
async def
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if sleep is None:
|
|
475
|
-
return
|
|
476
|
-
await asyncio.sleep(random.uniform(0.0, to_nanoseconds(sleep) / 1e9))
|
|
452
|
+
async def sleep(duration: Duration | None = None, /) -> None:
|
|
453
|
+
"""Sleep which accepts durations."""
|
|
454
|
+
if duration is not None:
|
|
455
|
+
await asyncio.sleep(in_seconds(duration))
|
|
477
456
|
|
|
478
457
|
|
|
479
458
|
##
|
|
480
459
|
|
|
481
460
|
|
|
482
|
-
async def
|
|
483
|
-
|
|
484
|
-
|
|
461
|
+
async def sleep_max(
|
|
462
|
+
duration: Duration | None = None, /, *, random: Random = SYSTEM_RANDOM
|
|
463
|
+
) -> None:
|
|
464
|
+
"""Sleep up to a maximum duration."""
|
|
465
|
+
if duration is not None:
|
|
466
|
+
await sleep(random.uniform(0.0, in_seconds(duration)))
|
|
485
467
|
|
|
486
468
|
|
|
487
469
|
##
|
|
488
470
|
|
|
489
471
|
|
|
490
|
-
async def
|
|
491
|
-
"""Sleep
|
|
492
|
-
|
|
493
|
-
return
|
|
494
|
-
await sleep(to_nanoseconds(delta) / 1e9)
|
|
472
|
+
async def sleep_rounded(delta: Delta, /) -> None:
|
|
473
|
+
"""Sleep until a rounded time."""
|
|
474
|
+
await sleep_until(round_date_or_date_time(get_now(), delta, mode="ceil"))
|
|
495
475
|
|
|
496
476
|
|
|
497
477
|
##
|
|
@@ -499,7 +479,7 @@ async def sleep_td(delta: Delta | None = None, /) -> None:
|
|
|
499
479
|
|
|
500
480
|
async def sleep_until(datetime: ZonedDateTime, /) -> None:
|
|
501
481
|
"""Sleep until a given time."""
|
|
502
|
-
await
|
|
482
|
+
await sleep(datetime - get_now())
|
|
503
483
|
|
|
504
484
|
|
|
505
485
|
##
|
|
@@ -550,16 +530,21 @@ async def _stream_one(
|
|
|
550
530
|
|
|
551
531
|
|
|
552
532
|
@asynccontextmanager
|
|
553
|
-
async def
|
|
554
|
-
|
|
533
|
+
async def timeout(
|
|
534
|
+
duration: Duration | None = None,
|
|
535
|
+
/,
|
|
536
|
+
*,
|
|
537
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
555
538
|
) -> AsyncIterator[None]:
|
|
556
|
-
"""Timeout context manager which accepts
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
539
|
+
"""Timeout context manager which accepts durations."""
|
|
540
|
+
if duration is None:
|
|
541
|
+
yield
|
|
542
|
+
else:
|
|
543
|
+
try:
|
|
544
|
+
async with asyncio.timeout(in_seconds(duration)):
|
|
545
|
+
yield
|
|
546
|
+
except TimeoutError:
|
|
547
|
+
raise error from None
|
|
563
548
|
|
|
564
549
|
|
|
565
550
|
##
|
|
@@ -599,17 +584,16 @@ __all__ = [
|
|
|
599
584
|
"OneAsyncNonUniqueError",
|
|
600
585
|
"StreamCommandOutput",
|
|
601
586
|
"chain_async",
|
|
602
|
-
"get_coroutine_name",
|
|
603
587
|
"get_items",
|
|
604
588
|
"get_items_nowait",
|
|
605
589
|
"one_async",
|
|
606
590
|
"put_items",
|
|
607
591
|
"put_items_nowait",
|
|
592
|
+
"sleep",
|
|
608
593
|
"sleep_max",
|
|
609
594
|
"sleep_rounded",
|
|
610
|
-
"sleep_td",
|
|
611
595
|
"sleep_until",
|
|
612
596
|
"stream_command",
|
|
613
|
-
"
|
|
597
|
+
"timeout",
|
|
614
598
|
"yield_locked_shelf",
|
|
615
599
|
]
|
utilities/atools.py
CHANGED
|
@@ -6,21 +6,21 @@ from pathlib import Path
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, cast, overload
|
|
7
7
|
|
|
8
8
|
import atools
|
|
9
|
-
from whenever import TimeDelta
|
|
10
9
|
|
|
11
|
-
from utilities.
|
|
10
|
+
from utilities.functions import in_seconds
|
|
11
|
+
from utilities.types import Coro, Duration, PathLike
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from atools._memoize_decorator import Keygen, Pickler
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
type _Key[**P, T] = tuple[Callable[P, Coro[T]],
|
|
17
|
+
type _Key[**P, T] = tuple[Callable[P, Coro[T]], Duration]
|
|
18
18
|
_MEMOIZED_FUNCS: dict[_Key, Callable[..., Coro[Any]]] = {}
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
async def call_memoized[**P, T](
|
|
22
22
|
func: Callable[P, Coro[T]],
|
|
23
|
-
refresh:
|
|
23
|
+
refresh: Duration | None = None,
|
|
24
24
|
/,
|
|
25
25
|
*args: P.args,
|
|
26
26
|
**kwargs: P.kwargs,
|
|
@@ -33,7 +33,7 @@ async def call_memoized[**P, T](
|
|
|
33
33
|
try:
|
|
34
34
|
memoized_func = _MEMOIZED_FUNCS[key]
|
|
35
35
|
except KeyError:
|
|
36
|
-
memoized_func = _MEMOIZED_FUNCS[(key)] = memoize(duration=
|
|
36
|
+
memoized_func = _MEMOIZED_FUNCS[(key)] = memoize(duration=in_seconds(refresh))(
|
|
37
37
|
func
|
|
38
38
|
)
|
|
39
39
|
return await memoized_func(*args, **kwargs)
|
|
@@ -48,7 +48,7 @@ def memoize[F: Callable[..., Coro[Any]]](
|
|
|
48
48
|
/,
|
|
49
49
|
*,
|
|
50
50
|
db_path: PathLike | None = None,
|
|
51
|
-
duration:
|
|
51
|
+
duration: Duration | None = None,
|
|
52
52
|
keygen: Keygen | None = None,
|
|
53
53
|
pickler: Pickler | None = None,
|
|
54
54
|
size: int | None = None,
|
|
@@ -59,7 +59,7 @@ def memoize[F: Callable[..., Coro[Any]]](
|
|
|
59
59
|
/,
|
|
60
60
|
*,
|
|
61
61
|
db_path: PathLike | None = None,
|
|
62
|
-
duration:
|
|
62
|
+
duration: Duration | None = None,
|
|
63
63
|
keygen: Keygen | None = None,
|
|
64
64
|
pickler: Pickler | None = None,
|
|
65
65
|
size: int | None = None,
|
|
@@ -69,7 +69,7 @@ def memoize[F: Callable[..., Coro[Any]]](
|
|
|
69
69
|
/,
|
|
70
70
|
*,
|
|
71
71
|
db_path: PathLike | None = None,
|
|
72
|
-
duration:
|
|
72
|
+
duration: Duration | None = None,
|
|
73
73
|
keygen: Keygen | None = None,
|
|
74
74
|
pickler: Pickler | None = None,
|
|
75
75
|
size: int | None = None,
|
|
@@ -87,9 +87,7 @@ def memoize[F: Callable[..., Coro[Any]]](
|
|
|
87
87
|
return cast("Callable[[F], F]", result)
|
|
88
88
|
return atools.memoize(
|
|
89
89
|
db_path=None if db_path is None else Path(db_path),
|
|
90
|
-
duration=duration
|
|
91
|
-
if isinstance(duration, TimeDelta)
|
|
92
|
-
else duration,
|
|
90
|
+
duration=None if duration is None else in_seconds(duration),
|
|
93
91
|
keygen=keygen,
|
|
94
92
|
pickler=pickler,
|
|
95
93
|
size=size,
|
utilities/cachetools.py
CHANGED
|
@@ -8,8 +8,10 @@ from typing import TYPE_CHECKING, Any, cast, override
|
|
|
8
8
|
import cachetools
|
|
9
9
|
from cachetools.func import ttl_cache
|
|
10
10
|
|
|
11
|
+
from utilities.functions import in_seconds
|
|
12
|
+
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
|
-
from
|
|
14
|
+
from utilities.types import Duration
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class TTLCache[K: Hashable, V](cachetools.TTLCache[K, V]):
|
|
@@ -19,13 +21,13 @@ class TTLCache[K: Hashable, V](cachetools.TTLCache[K, V]):
|
|
|
19
21
|
self,
|
|
20
22
|
*,
|
|
21
23
|
max_size: int | None = None,
|
|
22
|
-
max_duration:
|
|
24
|
+
max_duration: Duration | None = None,
|
|
23
25
|
timer: Callable[[], float] = monotonic,
|
|
24
26
|
get_size_of: Callable[[Any], int] | None = None,
|
|
25
27
|
) -> None:
|
|
26
28
|
super().__init__(
|
|
27
29
|
maxsize=inf if max_size is None else max_size,
|
|
28
|
-
ttl=inf if max_duration is None else
|
|
30
|
+
ttl=inf if max_duration is None else in_seconds(max_duration),
|
|
29
31
|
timer=timer,
|
|
30
32
|
getsizeof=get_size_of,
|
|
31
33
|
)
|
|
@@ -46,7 +48,7 @@ class TTLSet[T: Hashable](MutableSet[T]):
|
|
|
46
48
|
/,
|
|
47
49
|
*,
|
|
48
50
|
max_size: int | None = None,
|
|
49
|
-
max_duration:
|
|
51
|
+
max_duration: Duration | None = None,
|
|
50
52
|
timer: Callable[[], float] = monotonic,
|
|
51
53
|
get_size_of: Callable[[Any], int] | None = None,
|
|
52
54
|
) -> None:
|
|
@@ -95,7 +97,7 @@ class TTLSet[T: Hashable](MutableSet[T]):
|
|
|
95
97
|
def cache[F: Callable](
|
|
96
98
|
*,
|
|
97
99
|
max_size: int | None = None,
|
|
98
|
-
max_duration:
|
|
100
|
+
max_duration: Duration | None = None,
|
|
99
101
|
timer: Callable[[], float] = monotonic,
|
|
100
102
|
typed_: bool = False,
|
|
101
103
|
) -> Callable[[F], F]:
|
|
@@ -104,7 +106,7 @@ def cache[F: Callable](
|
|
|
104
106
|
"F",
|
|
105
107
|
ttl_cache(
|
|
106
108
|
maxsize=max_size,
|
|
107
|
-
ttl=inf if max_duration is None else
|
|
109
|
+
ttl=inf if max_duration is None else in_seconds(max_duration),
|
|
108
110
|
timer=timer,
|
|
109
111
|
typed=typed_,
|
|
110
112
|
),
|
utilities/click.py
CHANGED
|
@@ -11,9 +11,10 @@ import whenever
|
|
|
11
11
|
from click import Choice, Context, Parameter, ParamType
|
|
12
12
|
from click.types import IntParamType, StringParamType
|
|
13
13
|
|
|
14
|
+
from utilities.core import get_class, get_class_name, one
|
|
14
15
|
from utilities.enum import EnsureEnumError, ensure_enum
|
|
15
|
-
from utilities.functions import EnsureStrError, ensure_str
|
|
16
|
-
from utilities.iterables import is_iterable_not_str
|
|
16
|
+
from utilities.functions import EnsureStrError, ensure_str
|
|
17
|
+
from utilities.iterables import is_iterable_not_str
|
|
17
18
|
from utilities.parse import ParseObjectError, parse_object
|
|
18
19
|
from utilities.text import split_str
|
|
19
20
|
|
|
@@ -187,7 +188,7 @@ class EnumPartial[E: enum.Enum](ParamType):
|
|
|
187
188
|
case_sensitive: bool = False,
|
|
188
189
|
) -> None:
|
|
189
190
|
self._members = list(members)
|
|
190
|
-
self._enum =
|
|
191
|
+
self._enum = one({get_class(e) for e in self._members})
|
|
191
192
|
cls = get_class_name(self._enum)
|
|
192
193
|
self.name = f"enum-partial[{cls}]"
|
|
193
194
|
self._value = issubclass(self._enum, StrEnum) or value
|
utilities/concurrent.py
CHANGED