dycw-utilities 0.166.30__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.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +17 -10
- utilities/asyncio.py +50 -72
- utilities/atools.py +9 -11
- utilities/cachetools.py +16 -11
- utilities/click.py +76 -19
- 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 +387 -0
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +34 -265
- utilities/http.py +2 -3
- utilities/hypothesis.py +84 -29
- utilities/importlib.py +17 -1
- utilities/iterables.py +39 -575
- utilities/jinja2.py +145 -0
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +24 -24
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +42 -43
- utilities/os.py +4 -147
- utilities/packaging.py +129 -0
- utilities/parse.py +35 -15
- utilities/pathlib.py +3 -120
- utilities/platform.py +8 -90
- utilities/polars.py +38 -32
- utilities/postgres.py +37 -33
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +25 -0
- utilities/pydantic_settings.py +87 -16
- utilities/pydantic_settings_sops.py +16 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +96 -125
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +32 -11
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +57 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +2590 -0
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -99
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +31 -14
- utilities/traceback.py +16 -23
- utilities/types.py +42 -2
- utilities/typing.py +26 -14
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +53 -150
- dycw_utilities-0.166.30.dist-info/METADATA +0 -41
- dycw_utilities-0.166.30.dist-info/RECORD +0 -98
- dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
- utilities/aeventkit.py +0 -388
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/pickle.py +0 -25
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -77
- utilities/typed_settings.py +0 -152
- 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
|
@@ -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
|
|
@@ -94,7 +97,8 @@ def plot_dataframes(
|
|
|
94
97
|
# lines
|
|
95
98
|
selection = selection_point(bind="legend", fields=[var_name], nearest=False)
|
|
96
99
|
lines = [
|
|
97
|
-
chart
|
|
100
|
+
chart
|
|
101
|
+
.mark_line(interpolate=interpolate)
|
|
98
102
|
.encode(
|
|
99
103
|
x=x_use,
|
|
100
104
|
y=Y(value_name).scale(zero=False),
|
|
@@ -124,7 +128,8 @@ def plot_dataframes(
|
|
|
124
128
|
else:
|
|
125
129
|
tooltip_format_use = Undefined
|
|
126
130
|
rules = [
|
|
127
|
-
chart
|
|
131
|
+
chart
|
|
132
|
+
.transform_pivot(var_name, value=value_name, groupby=[x_use])
|
|
128
133
|
.mark_rule(color="gray")
|
|
129
134
|
.encode(
|
|
130
135
|
x=x_use,
|
|
@@ -143,7 +148,9 @@ def plot_dataframes(
|
|
|
143
148
|
]
|
|
144
149
|
zoom = selection_interval(bind="scales", encodings=["x"])
|
|
145
150
|
chart = (
|
|
146
|
-
|
|
151
|
+
vconcat_charts(*layers)
|
|
152
|
+
.add_params(zoom)
|
|
153
|
+
.resolve_scale(color="independent", x="shared")
|
|
147
154
|
)
|
|
148
155
|
if title is not None:
|
|
149
156
|
chart = chart.properties(title=title)
|
|
@@ -227,7 +234,8 @@ def plot_intraday_dataframe(
|
|
|
227
234
|
)
|
|
228
235
|
|
|
229
236
|
data4 = (
|
|
230
|
-
data3
|
|
237
|
+
data3
|
|
238
|
+
.group_by("_date_index")
|
|
231
239
|
.agg(
|
|
232
240
|
col(f"_{datetime}_index").min().alias(f"{datetime}_index_min"),
|
|
233
241
|
(col(f"_{datetime}_index").max() + 1).alias(f"{datetime}_index_max"),
|
|
@@ -264,7 +272,7 @@ def save_chart(
|
|
|
264
272
|
chart: _ChartLike, path: PathLike, /, *, overwrite: bool = False
|
|
265
273
|
) -> None:
|
|
266
274
|
"""Atomically save a chart to disk."""
|
|
267
|
-
with
|
|
275
|
+
with yield_write_path(path, overwrite=overwrite) as temp:
|
|
268
276
|
chart.save(temp, format="png")
|
|
269
277
|
|
|
270
278
|
|
|
@@ -282,8 +290,7 @@ def save_charts_as_pdf(
|
|
|
282
290
|
for chart, temp_path in zip(charts, temp_paths, strict=True):
|
|
283
291
|
save_chart(chart, temp_path)
|
|
284
292
|
data = ensure_bytes(convert(*temp_paths))
|
|
285
|
-
|
|
286
|
-
_ = temp.write_bytes(data)
|
|
293
|
+
write_bytes(path, data, overwrite=overwrite)
|
|
287
294
|
|
|
288
295
|
|
|
289
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
|
##
|
|
@@ -513,27 +493,21 @@ class StreamCommandOutput:
|
|
|
513
493
|
|
|
514
494
|
@property
|
|
515
495
|
def return_code(self) -> int:
|
|
516
|
-
return ensure_int(self.process.returncode)
|
|
496
|
+
return ensure_int(self.process.returncode)
|
|
517
497
|
|
|
518
498
|
|
|
519
499
|
async def stream_command(cmd: str, /) -> StreamCommandOutput:
|
|
520
500
|
"""Run a shell command asynchronously and stream its output in real time."""
|
|
521
|
-
process = await create_subprocess_shell(
|
|
522
|
-
|
|
523
|
-
)
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
)
|
|
527
|
-
proc_stderr = ensure_not_none( # skipif-not-windows
|
|
528
|
-
process.stderr, desc="process.stderr"
|
|
529
|
-
)
|
|
530
|
-
ret_stdout = StringIO() # skipif-not-windows
|
|
531
|
-
ret_stderr = StringIO() # skipif-not-windows
|
|
532
|
-
async with TaskGroup() as tg: # skipif-not-windows
|
|
501
|
+
process = await create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE)
|
|
502
|
+
proc_stdout = ensure_not_none(process.stdout, desc="process.stdout")
|
|
503
|
+
proc_stderr = ensure_not_none(process.stderr, desc="process.stderr")
|
|
504
|
+
ret_stdout = StringIO()
|
|
505
|
+
ret_stderr = StringIO()
|
|
506
|
+
async with TaskGroup() as tg:
|
|
533
507
|
_ = tg.create_task(_stream_one(proc_stdout, stdout, ret_stdout))
|
|
534
508
|
_ = tg.create_task(_stream_one(proc_stderr, stderr, ret_stderr))
|
|
535
|
-
_ = await process.wait()
|
|
536
|
-
return StreamCommandOutput(
|
|
509
|
+
_ = await process.wait()
|
|
510
|
+
return StreamCommandOutput(
|
|
537
511
|
process=process, stdout=ret_stdout.getvalue(), stderr=ret_stderr.getvalue()
|
|
538
512
|
)
|
|
539
513
|
|
|
@@ -542,7 +516,7 @@ async def _stream_one(
|
|
|
542
516
|
input_: StreamReader, out_stream: TextIO, ret_stream: StringIO, /
|
|
543
517
|
) -> None:
|
|
544
518
|
"""Asynchronously read from a stream and write to the target output stream."""
|
|
545
|
-
while True:
|
|
519
|
+
while True:
|
|
546
520
|
line = await input_.readline()
|
|
547
521
|
if not line:
|
|
548
522
|
break
|
|
@@ -556,16 +530,21 @@ async def _stream_one(
|
|
|
556
530
|
|
|
557
531
|
|
|
558
532
|
@asynccontextmanager
|
|
559
|
-
async def
|
|
560
|
-
|
|
533
|
+
async def timeout(
|
|
534
|
+
duration: Duration | None = None,
|
|
535
|
+
/,
|
|
536
|
+
*,
|
|
537
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
561
538
|
) -> AsyncIterator[None]:
|
|
562
|
-
"""Timeout context manager which accepts
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
|
569
548
|
|
|
570
549
|
|
|
571
550
|
##
|
|
@@ -605,17 +584,16 @@ __all__ = [
|
|
|
605
584
|
"OneAsyncNonUniqueError",
|
|
606
585
|
"StreamCommandOutput",
|
|
607
586
|
"chain_async",
|
|
608
|
-
"get_coroutine_name",
|
|
609
587
|
"get_items",
|
|
610
588
|
"get_items_nowait",
|
|
611
589
|
"one_async",
|
|
612
590
|
"put_items",
|
|
613
591
|
"put_items_nowait",
|
|
592
|
+
"sleep",
|
|
614
593
|
"sleep_max",
|
|
615
594
|
"sleep_rounded",
|
|
616
|
-
"sleep_td",
|
|
617
595
|
"sleep_until",
|
|
618
596
|
"stream_command",
|
|
619
|
-
"
|
|
597
|
+
"timeout",
|
|
620
598
|
"yield_locked_shelf",
|
|
621
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
|
@@ -3,13 +3,15 @@ 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
|
|
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,16 +97,19 @@ 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]:
|
|
102
104
|
"""Decorate a function with `max_size` and/or `ttl` settings."""
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
return cast(
|
|
106
|
+
"F",
|
|
107
|
+
ttl_cache(
|
|
108
|
+
maxsize=max_size,
|
|
109
|
+
ttl=inf if max_duration is None else in_seconds(max_duration),
|
|
110
|
+
timer=timer,
|
|
111
|
+
typed=typed_,
|
|
112
|
+
),
|
|
108
113
|
)
|
|
109
114
|
|
|
110
115
|
|