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.
Files changed (94) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
  4. utilities/__init__.py +1 -1
  5. utilities/altair.py +8 -6
  6. utilities/asyncio.py +40 -56
  7. utilities/atools.py +9 -11
  8. utilities/cachetools.py +8 -6
  9. utilities/click.py +4 -3
  10. utilities/concurrent.py +1 -1
  11. utilities/constants.py +492 -0
  12. utilities/contextlib.py +23 -30
  13. utilities/contextvars.py +1 -23
  14. utilities/core.py +2581 -0
  15. utilities/dataclasses.py +16 -119
  16. utilities/docker.py +139 -45
  17. utilities/enum.py +1 -1
  18. utilities/errors.py +2 -16
  19. utilities/fastapi.py +5 -5
  20. utilities/fpdf2.py +2 -1
  21. utilities/functions.py +33 -264
  22. utilities/http.py +2 -3
  23. utilities/hypothesis.py +48 -25
  24. utilities/iterables.py +39 -575
  25. utilities/jinja2.py +3 -6
  26. utilities/jupyter.py +5 -3
  27. utilities/libcst.py +1 -1
  28. utilities/lightweight_charts.py +4 -6
  29. utilities/logging.py +17 -15
  30. utilities/math.py +1 -36
  31. utilities/more_itertools.py +4 -6
  32. utilities/numpy.py +2 -1
  33. utilities/operator.py +2 -2
  34. utilities/orjson.py +24 -25
  35. utilities/os.py +4 -185
  36. utilities/packaging.py +129 -0
  37. utilities/parse.py +33 -13
  38. utilities/pathlib.py +2 -136
  39. utilities/platform.py +8 -90
  40. utilities/polars.py +34 -31
  41. utilities/postgres.py +9 -4
  42. utilities/pottery.py +20 -18
  43. utilities/pqdm.py +3 -4
  44. utilities/psutil.py +2 -3
  45. utilities/pydantic.py +18 -4
  46. utilities/pydantic_settings.py +7 -9
  47. utilities/pydantic_settings_sops.py +3 -3
  48. utilities/pyinstrument.py +4 -4
  49. utilities/pytest.py +49 -108
  50. utilities/pytest_plugins/pytest_regressions.py +2 -2
  51. utilities/pytest_regressions.py +8 -6
  52. utilities/random.py +2 -8
  53. utilities/redis.py +98 -94
  54. utilities/reprlib.py +11 -118
  55. utilities/shellingham.py +66 -0
  56. utilities/slack_sdk.py +13 -12
  57. utilities/sqlalchemy.py +42 -30
  58. utilities/sqlalchemy_polars.py +16 -25
  59. utilities/subprocess.py +1166 -148
  60. utilities/tabulate.py +32 -0
  61. utilities/testbook.py +8 -8
  62. utilities/text.py +24 -115
  63. utilities/throttle.py +159 -0
  64. utilities/time.py +18 -0
  65. utilities/timer.py +29 -12
  66. utilities/traceback.py +15 -22
  67. utilities/types.py +38 -3
  68. utilities/typing.py +18 -12
  69. utilities/uuid.py +1 -1
  70. utilities/version.py +202 -45
  71. utilities/whenever.py +22 -150
  72. dycw_utilities-0.175.17.dist-info/METADATA +0 -34
  73. dycw_utilities-0.175.17.dist-info/RECORD +0 -103
  74. utilities/atomicwrites.py +0 -182
  75. utilities/cryptography.py +0 -41
  76. utilities/getpass.py +0 -8
  77. utilities/git.py +0 -19
  78. utilities/grp.py +0 -28
  79. utilities/gzip.py +0 -31
  80. utilities/json.py +0 -70
  81. utilities/permissions.py +0 -298
  82. utilities/pickle.py +0 -25
  83. utilities/pwd.py +0 -28
  84. utilities/re.py +0 -156
  85. utilities/sentinel.py +0 -73
  86. utilities/socket.py +0 -8
  87. utilities/string.py +0 -20
  88. utilities/tempfile.py +0 -136
  89. utilities/tzdata.py +0 -11
  90. utilities/tzlocal.py +0 -28
  91. utilities/warnings.py +0 -65
  92. utilities/zipfile.py +0 -25
  93. utilities/zoneinfo.py +0 -133
  94. {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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.21
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.175.17"
3
+ __version__ = "0.185.8"
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.atomicwrites import writer
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 writer(path, overwrite=overwrite) as temp:
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
- with writer(path, overwrite=overwrite) as temp:
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.functions import ensure_int, ensure_not_none
40
- from utilities.os import is_pytest
41
- from utilities.random import SYSTEM_RANDOM
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.warnings import suppress_warnings
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: Delta | None
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: Delta | None = None,
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 timeout_td(self._timeout, error=self._error):
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) {get_repr(self.iterables)} must not be empty"
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) {get_repr(self.iterables)} must contain exactly one item; got {self.first}, {self.second} and perhaps more"
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 sleep_max(
471
- sleep: Delta | None = None, /, *, random: Random = SYSTEM_RANDOM
472
- ) -> None:
473
- """Sleep which accepts deltas."""
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 sleep_rounded(delta: Delta, /) -> None:
483
- """Sleep until a rounded time."""
484
- await sleep_until(round_date_or_date_time(get_now(), delta, mode="ceil"))
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 sleep_td(delta: Delta | None = None, /) -> None:
491
- """Sleep which accepts deltas."""
492
- if delta is None:
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 sleep_td(datetime - get_now())
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 timeout_td(
554
- timeout: Delta | None = None, /, *, error: MaybeType[BaseException] = TimeoutError
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 deltas."""
557
- timeout_use = None if timeout is None else (to_nanoseconds(timeout) / 1e9)
558
- try:
559
- async with asyncio.timeout(timeout_use):
560
- yield
561
- except TimeoutError:
562
- raise error from None
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
- "timeout_td",
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.types import Coro, PathLike
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]], TimeDelta]
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: TimeDelta | None = None,
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=refresh.in_seconds())(
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: float | TimeDelta | None = None,
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: float | TimeDelta | None = None,
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: float | TimeDelta | None = None,
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.py_timedelta()
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 whenever import TimeDelta
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: TimeDelta | None = None,
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 max_duration.in_seconds(),
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: TimeDelta | None = None,
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: TimeDelta | None = None,
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 max_duration.in_seconds(),
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, get_class, get_class_name
16
- from utilities.iterables import is_iterable_not_str, one_unique
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 = one_unique(get_class(e) for e in self._members)
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
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
  from collections.abc import Callable, Iterable
13
13
  from multiprocessing.context import BaseContext
14
14
 
15
- from utilities.os import IntOrAll
15
+ from utilities.types import IntOrAll
16
16
 
17
17
 
18
18
  def concurrent_map[T](