dycw-utilities 0.166.12__tar.gz → 0.173.4__tar.gz

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.

Files changed (228) hide show
  1. dycw_utilities-0.173.4/PKG-INFO +41 -0
  2. dycw_utilities-0.173.4/pyproject.toml +330 -0
  3. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/__init__.py +1 -1
  4. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/aeventkit.py +2 -1
  5. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/altair.py +7 -6
  6. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/asyncio.py +73 -16
  7. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/cachetools.py +9 -6
  8. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/click.py +80 -23
  9. dycw_utilities-0.173.4/src/utilities/docker.py +268 -0
  10. dycw_utilities-0.173.4/src/utilities/grp.py +28 -0
  11. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/hypothesis.py +7 -13
  12. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/iterables.py +1 -1
  13. dycw_utilities-0.173.4/src/utilities/jinja2.py +148 -0
  14. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/libcst.py +1 -1
  15. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/logging.py +7 -9
  16. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/orjson.py +18 -18
  17. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/os.py +39 -1
  18. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/parse.py +2 -2
  19. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pathlib.py +17 -0
  20. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/platform.py +2 -2
  21. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/polars.py +140 -2
  22. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/postgres.py +28 -29
  23. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pottery.py +2 -2
  24. dycw_utilities-0.173.4/src/utilities/pwd.py +28 -0
  25. dycw_utilities-0.173.4/src/utilities/pydantic.py +11 -0
  26. dycw_utilities-0.173.4/src/utilities/pydantic_settings.py +240 -0
  27. dycw_utilities-0.173.4/src/utilities/pydantic_settings_sops.py +76 -0
  28. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pytest.py +151 -33
  29. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pytest_plugins/pytest_regressions.py +6 -2
  30. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pytest_regressions.py +1 -2
  31. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/redis.py +2 -2
  32. dycw_utilities-0.173.4/src/utilities/shutil.py +25 -0
  33. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/sqlalchemy.py +16 -1
  34. dycw_utilities-0.173.4/src/utilities/subprocess.py +308 -0
  35. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/tempfile.py +36 -1
  36. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/text.py +54 -38
  37. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/timer.py +2 -2
  38. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/traceback.py +3 -3
  39. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/typing.py +8 -2
  40. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/whenever.py +36 -5
  41. dycw_utilities-0.166.12/.gitignore +0 -169
  42. dycw_utilities-0.166.12/LICENSE +0 -21
  43. dycw_utilities-0.166.12/PKG-INFO +0 -41
  44. dycw_utilities-0.166.12/pyproject.toml +0 -494
  45. dycw_utilities-0.166.12/src/tests/__init__.py +0 -0
  46. dycw_utilities-0.166.12/src/tests/conftest.py +0 -193
  47. dycw_utilities-0.166.12/src/tests/modules/package_missing/__init__.py +0 -1
  48. dycw_utilities-0.166.12/src/tests/modules/package_missing/module.py +0 -5
  49. dycw_utilities-0.166.12/src/tests/modules/package_with/__init__.py +0 -1
  50. dycw_utilities-0.166.12/src/tests/modules/package_with/outer_1.py +0 -17
  51. dycw_utilities-0.166.12/src/tests/modules/package_with/outer_2.py +0 -17
  52. dycw_utilities-0.166.12/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  53. dycw_utilities-0.166.12/src/tests/modules/package_with/subpackage/inner_1.py +0 -17
  54. dycw_utilities-0.166.12/src/tests/modules/package_with/subpackage/inner_2.py +0 -17
  55. dycw_utilities-0.166.12/src/tests/modules/package_with/subpackage/inner_3.py +0 -17
  56. dycw_utilities-0.166.12/src/tests/modules/package_without/__init__.py +0 -1
  57. dycw_utilities-0.166.12/src/tests/modules/package_without/module_1.py +0 -17
  58. dycw_utilities-0.166.12/src/tests/modules/package_without/module_2.py +0 -17
  59. dycw_utilities-0.166.12/src/tests/modules/standalone.py +0 -17
  60. dycw_utilities-0.166.12/src/tests/modules/with_imports.py +0 -7
  61. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -1
  62. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -26
  63. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -1
  64. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -1
  65. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -1
  66. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -5
  67. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -25
  68. dycw_utilities-0.166.12/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -26
  69. dycw_utilities-0.166.12/src/tests/test_aeventkit.py +0 -282
  70. dycw_utilities-0.166.12/src/tests/test_altair.py +0 -169
  71. dycw_utilities-0.166.12/src/tests/test_asyncio.py +0 -433
  72. dycw_utilities-0.166.12/src/tests/test_atomicwrites.py +0 -198
  73. dycw_utilities-0.166.12/src/tests/test_atools.py +0 -67
  74. dycw_utilities-0.166.12/src/tests/test_cachetools.py +0 -85
  75. dycw_utilities-0.166.12/src/tests/test_click.py +0 -439
  76. dycw_utilities-0.166.12/src/tests/test_concurrent.py +0 -86
  77. dycw_utilities-0.166.12/src/tests/test_contextlib.py +0 -242
  78. dycw_utilities-0.166.12/src/tests/test_contextvars.py +0 -37
  79. dycw_utilities-0.166.12/src/tests/test_cryptography.py +0 -38
  80. dycw_utilities-0.166.12/src/tests/test_cvxpy.py +0 -649
  81. dycw_utilities-0.166.12/src/tests/test_dataclasses.py +0 -1012
  82. dycw_utilities-0.166.12/src/tests/test_enum.py +0 -222
  83. dycw_utilities-0.166.12/src/tests/test_errors.py +0 -82
  84. dycw_utilities-0.166.12/src/tests/test_fastapi.py +0 -43
  85. dycw_utilities-0.166.12/src/tests/test_fpdf2.py +0 -22
  86. dycw_utilities-0.166.12/src/tests/test_functions.py +0 -778
  87. dycw_utilities-0.166.12/src/tests/test_functools.py +0 -59
  88. dycw_utilities-0.166.12/src/tests/test_getpass.py +0 -8
  89. dycw_utilities-0.166.12/src/tests/test_git.py +0 -19
  90. dycw_utilities-0.166.12/src/tests/test_gzip.py +0 -21
  91. dycw_utilities-0.166.12/src/tests/test_hashlib.py +0 -14
  92. dycw_utilities-0.166.12/src/tests/test_http.py +0 -20
  93. dycw_utilities-0.166.12/src/tests/test_hypothesis.py +0 -1272
  94. dycw_utilities-0.166.12/src/tests/test_importlib.py +0 -22
  95. dycw_utilities-0.166.12/src/tests/test_inflect.py +0 -29
  96. dycw_utilities-0.166.12/src/tests/test_ipython.py +0 -15
  97. dycw_utilities-0.166.12/src/tests/test_iterables.py +0 -1394
  98. dycw_utilities-0.166.12/src/tests/test_json.py +0 -52
  99. dycw_utilities-0.166.12/src/tests/test_jupyter.py +0 -64
  100. dycw_utilities-0.166.12/src/tests/test_libcst.py +0 -168
  101. dycw_utilities-0.166.12/src/tests/test_lightweight_charts.py +0 -64
  102. dycw_utilities-0.166.12/src/tests/test_logging.py +0 -534
  103. dycw_utilities-0.166.12/src/tests/test_math.py +0 -1383
  104. dycw_utilities-0.166.12/src/tests/test_memory_profiler.py +0 -21
  105. dycw_utilities-0.166.12/src/tests/test_modules.py +0 -144
  106. dycw_utilities-0.166.12/src/tests/test_more_itertools.py +0 -354
  107. dycw_utilities-0.166.12/src/tests/test_numpy.py +0 -1178
  108. dycw_utilities-0.166.12/src/tests/test_objects/__init__.py +0 -1
  109. dycw_utilities-0.166.12/src/tests/test_objects/objects.py +0 -225
  110. dycw_utilities-0.166.12/src/tests/test_operator.py +0 -127
  111. dycw_utilities-0.166.12/src/tests/test_optuna.py +0 -76
  112. dycw_utilities-0.166.12/src/tests/test_orjson.py +0 -799
  113. dycw_utilities-0.166.12/src/tests/test_os.py +0 -160
  114. dycw_utilities-0.166.12/src/tests/test_parse.py +0 -756
  115. dycw_utilities-0.166.12/src/tests/test_pathlib.py +0 -325
  116. dycw_utilities-0.166.12/src/tests/test_pickle.py +0 -21
  117. dycw_utilities-0.166.12/src/tests/test_platform.py +0 -97
  118. dycw_utilities-0.166.12/src/tests/test_polars.py +0 -3100
  119. dycw_utilities-0.166.12/src/tests/test_polars_ols.py +0 -110
  120. dycw_utilities-0.166.12/src/tests/test_postgres.py +0 -162
  121. dycw_utilities-0.166.12/src/tests/test_pottery.py +0 -103
  122. dycw_utilities-0.166.12/src/tests/test_pqdm.py +0 -105
  123. dycw_utilities-0.166.12/src/tests/test_psutil.py +0 -24
  124. dycw_utilities-0.166.12/src/tests/test_pydantic_settings.py +0 -77
  125. dycw_utilities-0.166.12/src/tests/test_pydantic_settings_sops.py +0 -49
  126. dycw_utilities-0.166.12/src/tests/test_pyinstrument.py +0 -19
  127. dycw_utilities-0.166.12/src/tests/test_pytest.py +0 -436
  128. dycw_utilities-0.166.12/src/tests/test_pytest_randomly.py +0 -8
  129. dycw_utilities-0.166.12/src/tests/test_pytest_regressions.py +0 -68
  130. dycw_utilities-0.166.12/src/tests/test_random.py +0 -85
  131. dycw_utilities-0.166.12/src/tests/test_re.py +0 -133
  132. dycw_utilities-0.166.12/src/tests/test_redis.py +0 -768
  133. dycw_utilities-0.166.12/src/tests/test_reprlib.py +0 -87
  134. dycw_utilities-0.166.12/src/tests/test_scipy.py +0 -48
  135. dycw_utilities-0.166.12/src/tests/test_sentinel.py +0 -53
  136. dycw_utilities-0.166.12/src/tests/test_shelve.py +0 -26
  137. dycw_utilities-0.166.12/src/tests/test_slack_sdk.py +0 -34
  138. dycw_utilities-0.166.12/src/tests/test_socket.py +0 -8
  139. dycw_utilities-0.166.12/src/tests/test_sqlalchemy.py +0 -1452
  140. dycw_utilities-0.166.12/src/tests/test_sqlalchemy_polars.py +0 -638
  141. dycw_utilities-0.166.12/src/tests/test_statsmodels.py +0 -51
  142. dycw_utilities-0.166.12/src/tests/test_string.py +0 -51
  143. dycw_utilities-0.166.12/src/tests/test_tempfile.py +0 -31
  144. dycw_utilities-0.166.12/src/tests/test_testbook.py +0 -38
  145. dycw_utilities-0.166.12/src/tests/test_text.py +0 -377
  146. dycw_utilities-0.166.12/src/tests/test_threading.py +0 -42
  147. dycw_utilities-0.166.12/src/tests/test_timer.py +0 -130
  148. dycw_utilities-0.166.12/src/tests/test_traceback.py +0 -168
  149. dycw_utilities-0.166.12/src/tests/test_typed_settings.py +0 -321
  150. dycw_utilities-0.166.12/src/tests/test_types.py +0 -53
  151. dycw_utilities-0.166.12/src/tests/test_typing.py +0 -1166
  152. dycw_utilities-0.166.12/src/tests/test_typing_funcs/__init__.py +0 -0
  153. dycw_utilities-0.166.12/src/tests/test_typing_funcs/no_future.py +0 -45
  154. dycw_utilities-0.166.12/src/tests/test_typing_funcs/with_future.py +0 -246
  155. dycw_utilities-0.166.12/src/tests/test_tzdata.py +0 -14
  156. dycw_utilities-0.166.12/src/tests/test_tzlocal.py +0 -17
  157. dycw_utilities-0.166.12/src/tests/test_uuid.py +0 -96
  158. dycw_utilities-0.166.12/src/tests/test_version.py +0 -142
  159. dycw_utilities-0.166.12/src/tests/test_warnings.py +0 -74
  160. dycw_utilities-0.166.12/src/tests/test_whenever.py +0 -1805
  161. dycw_utilities-0.166.12/src/tests/test_zipfile.py +0 -37
  162. dycw_utilities-0.166.12/src/tests/test_zoneinfo.py +0 -131
  163. dycw_utilities-0.166.12/src/utilities/pydantic_settings.py +0 -61
  164. dycw_utilities-0.166.12/src/utilities/pydantic_settings_sops.py +0 -40
  165. dycw_utilities-0.166.12/src/utilities/pytest_plugins/__init__.py +0 -1
  166. dycw_utilities-0.166.12/src/utilities/typed_settings.py +0 -152
  167. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/README.md +0 -0
  168. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/atomicwrites.py +0 -0
  169. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/atools.py +0 -0
  170. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/concurrent.py +0 -0
  171. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/contextlib.py +0 -0
  172. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/contextvars.py +0 -0
  173. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/cryptography.py +0 -0
  174. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/cvxpy.py +0 -0
  175. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/dataclasses.py +0 -0
  176. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/enum.py +0 -0
  177. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/errors.py +0 -0
  178. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/fastapi.py +0 -0
  179. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/fpdf2.py +0 -0
  180. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/functions.py +0 -0
  181. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/functools.py +0 -0
  182. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/getpass.py +0 -0
  183. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/git.py +0 -0
  184. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/gzip.py +0 -0
  185. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/hashlib.py +0 -0
  186. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/http.py +0 -0
  187. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/importlib.py +0 -0
  188. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/inflect.py +0 -0
  189. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/ipython.py +0 -0
  190. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/json.py +0 -0
  191. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/jupyter.py +0 -0
  192. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/lightweight_charts.py +0 -0
  193. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/math.py +0 -0
  194. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/memory_profiler.py +0 -0
  195. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/modules.py +0 -0
  196. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/more_itertools.py +0 -0
  197. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/numpy.py +0 -0
  198. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/operator.py +0 -0
  199. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/optuna.py +0 -0
  200. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pickle.py +0 -0
  201. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/polars_ols.py +0 -0
  202. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pqdm.py +0 -0
  203. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/psutil.py +0 -0
  204. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/py.typed +0 -0
  205. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pyinstrument.py +0 -0
  206. {dycw_utilities-0.166.12/src/tests/modules → dycw_utilities-0.173.4/src/utilities/pytest_plugins}/__init__.py +0 -0
  207. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  208. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/random.py +0 -0
  209. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/re.py +0 -0
  210. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/reprlib.py +0 -0
  211. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/scipy.py +0 -0
  212. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/sentinel.py +0 -0
  213. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/shelve.py +0 -0
  214. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/slack_sdk.py +0 -0
  215. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/socket.py +0 -0
  216. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/sqlalchemy_polars.py +0 -0
  217. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/statsmodels.py +0 -0
  218. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/string.py +0 -0
  219. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/testbook.py +0 -0
  220. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/threading.py +0 -0
  221. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/types.py +0 -0
  222. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/tzdata.py +0 -0
  223. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/tzlocal.py +0 -0
  224. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/uuid.py +0 -0
  225. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/version.py +0 -0
  226. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/warnings.py +0 -0
  227. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.166.12 → dycw_utilities-0.173.4}/src/utilities/zoneinfo.py +0 -0
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.3
2
+ Name: dycw-utilities
3
+ Version: 0.173.4
4
+ Author: Derek Wan
5
+ Author-email: Derek Wan <d.wan@icloud.com>
6
+ Requires-Dist: atomicwrites>=1.4.1,<1.5
7
+ Requires-Dist: typing-extensions>=4.15.0,<4.16
8
+ Requires-Dist: tzlocal>=5.3.1,<5.4
9
+ Requires-Dist: whenever>=0.9.4,<0.10
10
+ Requires-Dist: coloredlogs>=15.0.1,<15.1 ; extra == 'logging'
11
+ Requires-Dist: dycw-pytest-only>=2.1.1,<2.2 ; extra == 'test'
12
+ Requires-Dist: hypothesis>=6.148.8,<6.149 ; extra == 'test'
13
+ Requires-Dist: pytest>=9.0.2,<9.1 ; extra == 'test'
14
+ Requires-Dist: pytest-asyncio>=1.3.0,<1.4 ; extra == 'test'
15
+ Requires-Dist: pytest-cov>=7.0.0,<7.1 ; extra == 'test'
16
+ Requires-Dist: pytest-instafail>=0.5.0,<0.6 ; extra == 'test'
17
+ Requires-Dist: pytest-lazy-fixtures>=1.4.0,<1.5 ; extra == 'test'
18
+ Requires-Dist: pytest-randomly>=4.0.1,<4.1 ; extra == 'test'
19
+ Requires-Dist: pytest-regressions>=2.8.3,<2.9 ; extra == 'test'
20
+ Requires-Dist: pytest-repeat>=0.9.4,<0.10 ; extra == 'test'
21
+ Requires-Dist: pytest-rerunfailures>=16.1,<16.2 ; extra == 'test'
22
+ Requires-Dist: pytest-rng>=1.0.0,<1.1 ; extra == 'test'
23
+ Requires-Dist: pytest-timeout>=2.4.0,<2.5 ; extra == 'test'
24
+ Requires-Dist: pytest-xdist>=3.8.0,<3.9 ; extra == 'test'
25
+ Requires-Dist: testbook>=0.4.2,<0.5 ; extra == 'test'
26
+ Requires-Python: >=3.12
27
+ Provides-Extra: logging
28
+ Provides-Extra: test
29
+ Description-Content-Type: text/markdown
30
+
31
+ [![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities)
32
+
33
+ # `dycw-utilities`
34
+
35
+ [All the Python functions I don't want to write twice.](https://github.com/nvim-lua/plenary.nvim)
36
+
37
+ ## Installation
38
+
39
+ - `pip install dycw-utilities`
40
+
41
+ or with [extras](https://github.com/dycw/python-utilities/blob/master/pyproject.toml).
@@ -0,0 +1,330 @@
1
+ # build-system
2
+ [build-system]
3
+ build-backend = "uv_build"
4
+ requires = ["uv_build"]
5
+
6
+ # dependency groups
7
+ [dependency-groups]
8
+ aeventkit = ["aeventkit >=2.1.0, <2.2"]
9
+ altair = ["altair >=5.5.0, <5.6"]
10
+ altair-test = ["polars", "img2pdf", "vl-convert-python"]
11
+ atools = ["atools >=0.14.2, <0.15"]
12
+ cachetools = ["cachetools >=6.2.4, <6.3"]
13
+ click = ["click >=8.3.1, <8.4"]
14
+ core = [
15
+ "atomicwrites >=1.4.1, <1.5",
16
+ "typing-extensions >=4.15.0, <4.16",
17
+ "tzlocal >=5.3.1, <5.4",
18
+ "whenever >=0.9.4, <0.10",
19
+ ]
20
+ cryptography = ["cryptography >=46.0.3, <46.1"]
21
+ cvxpy = ["cvxpy >=1.7.5, <1.8"]
22
+ dataclasses-test = ["orjson", "polars"]
23
+ dev = [
24
+ "coloredlogs >=15.0.1, <15.1",
25
+ "coverage-conditional-plugin >=0.9.0, <0.10",
26
+ "dycw-pytest-only >=2.1.1, <2.2",
27
+ "pyright[nodejs] >=1.1.407, <1.2",
28
+ "pytest-cov >=7.0.0, <7.1",
29
+ "pytest-timeout >=2.4.0, <2.5",
30
+ ]
31
+ fastapi = ["fastapi >=0.127.0, <0.128"]
32
+ fastapi-test = ["httpx", "uvicorn"]
33
+ fpdf2 = ["fpdf2 >=2.8.5, <2.9"]
34
+ gitpython = ["gitpython >=3.1.45, <3.2"]
35
+ hashlib-test = ["orjson"]
36
+ http-test = ["orjson"]
37
+ hypothesis = ["hypothesis >=6.148.8, <6.149"]
38
+ hypothesis-test = ["libcst", "numpy", "pathvalidate", "pytest-rerunfailures"]
39
+ inflect = ["inflect >=7.5.0, <7.6"]
40
+ jinja2 = ["jinja2 >=3.1.6, <3.2"]
41
+ jupyter-test = ["pandas", "polars"]
42
+ libcst = ["libcst >=1.8.6, <1.9"]
43
+ lightweight-charts = ["lightweight-charts >=2.1, <2.2"]
44
+ lightweight-charts-test = ["polars", "pyarrow"]
45
+ math-test = ["numpy"]
46
+ memory-profiler = ["memory-profiler >=0.61.0, <0.62"]
47
+ more-itertools = ["more-itertools >=10.8.0, <10.9"]
48
+ numpy = ["numpy >=2.4.0, <2.5"]
49
+ operator = ["polars"]
50
+ optuna = ["optuna >=4.6.0, <4.7"]
51
+ orjson = ["orjson >=3.11.5, <3.12"]
52
+ orjson-test = ["polars"]
53
+ polars = ["polars >=1.36.1, <1.37"]
54
+ polars-ols = ["polars-ols >=0.3.5, <0.4"]
55
+ polars-ols-test = ["scikit-learn"]
56
+ polars-test = ["numpy", "scipy", "statsmodels"]
57
+ pottery = ["pottery >=3.0.1, <3.1"]
58
+ pottery-test = ["orjson"]
59
+ pqdm = ["pqdm >=0.2.0, <0.3"]
60
+ psutil = ["psutil >=7.2.0, <7.3"]
61
+ pydantic = ["pydantic >=2.12.5, <2.13"]
62
+ pydantic-settings = ["pydantic-settings >=2.12.0, <2.13"]
63
+ pydantic-settings-sops = ["pydantic-settings-sops"]
64
+ pydantic-settings-test = ["pyyaml", "tomlkit"]
65
+ pyinstrument = ["pyinstrument >=5.1.1, <5.2"]
66
+ pytest = [
67
+ "pytest >=9.0.2, <9.1",
68
+ "pytest-asyncio >=1.3.0, <1.4",
69
+ "pytest-randomly >=4.0.1, <4.1",
70
+ "pytest-timeout >=2.4.0, <2.5",
71
+ "pytest-xdist >=3.8.0, <3.9",
72
+ ]
73
+ pytest-regressions = ["pytest-regressions >=2.8.3, <2.9"]
74
+ pytest-regressions-test = ["orjson", "polars"]
75
+ pytest-test = ["orjson", "pytest-rerunfailures", "pytest-rng"]
76
+ redis = ["redis >=7.1.0, <7.2", "orjson"]
77
+ redis-test = ["pytest-rerunfailures"]
78
+ reprlib-test = ["rich"]
79
+ scipy = ["scipy >=1.16.3, <1.17"]
80
+ sklearn = ["scikit-learn >=1.8.0, <1.9"]
81
+ slack-sdk = ["slack-sdk >=3.39.0, <3.40"]
82
+ slack-sdk-test = ["aiohttp"]
83
+ sqlalchemy = ["sqlalchemy >=2.0.45, <2.1", "psycopg"]
84
+ sqlalchemy-polars = ["sqlalchemy", "polars"]
85
+ sqlalchemy-polars-test = ["aiosqlite", "asyncpg", "greenlet"]
86
+ sqlalchemy-test = ["aiosqlite", "asyncpg", "greenlet"]
87
+ statsmodels = ["statsmodels >=0.14.6, <0.15"]
88
+ testbook = ["testbook >=0.4.2, <0.5"]
89
+ tzdata = ["tzdata >=2025.3, <2025.4"]
90
+ whenever-test = ["pathvalidate"]
91
+
92
+ # project
93
+ [project]
94
+ authors = [{ email = "d.wan@icloud.com", name = "Derek Wan" }]
95
+ dependencies = [
96
+ "atomicwrites >=1.4.1, <1.5",
97
+ "typing-extensions >=4.15.0, <4.16",
98
+ "tzlocal >=5.3.1, <5.4",
99
+ "whenever >=0.9.4, <0.10",
100
+ ]
101
+ name = "dycw-utilities"
102
+ readme = "README.md"
103
+ requires-python = ">= 3.12"
104
+ version = "0.173.4"
105
+
106
+ [project.entry-points.pytest11]
107
+ pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
108
+ pytest-regressions = "utilities.pytest_plugins.pytest_regressions"
109
+
110
+ [project.optional-dependencies]
111
+ logging = ["coloredlogs >=15.0.1, <15.1"]
112
+ test = [
113
+ "dycw-pytest-only >=2.1.1, <2.2",
114
+ "hypothesis >=6.148.8, <6.149",
115
+ "pytest >=9.0.2, <9.1",
116
+ "pytest-asyncio >=1.3.0, <1.4",
117
+ "pytest-cov >=7.0.0, <7.1",
118
+ "pytest-instafail >=0.5.0, <0.6",
119
+ "pytest-lazy-fixtures >=1.4.0, <1.5",
120
+ "pytest-randomly >=4.0.1, <4.1",
121
+ "pytest-regressions >=2.8.3, <2.9",
122
+ "pytest-repeat >=0.9.4, <0.10",
123
+ "pytest-rerunfailures >=16.1, <16.2",
124
+ "pytest-rng >=1.0.0, <1.1",
125
+ "pytest-timeout >=2.4.0, <2.5",
126
+ "pytest-xdist >=3.8.0, <3.9",
127
+ "testbook >=0.4.2, <0.5",
128
+ ]
129
+
130
+ [project.scripts]
131
+
132
+ # tool
133
+ [tool]
134
+
135
+ # bump-my-version
136
+ [tool.bumpversion]
137
+ allow_dirty = true
138
+ current_version = "0.173.4"
139
+
140
+ [[tool.bumpversion.files]]
141
+ filename = "src/utilities/__init__.py"
142
+ replace = "__version__ = \"{new_version}\""
143
+ search = "__version__ = \"{current_version}\""
144
+
145
+ # coverage
146
+ [tool.coverage]
147
+ [tool.coverage.coverage_conditional_plugin]
148
+ [tool.coverage.coverage_conditional_plugin.rules]
149
+ skipif-ci = '"CI" in os_environ'
150
+ skipif-ci-and-mac = '("CI" in os_environ) and (sys_platform == "darwin")'
151
+ skipif-ci-and-not-linux = '("CI" in os_environ) and (sys_platform != "linux")'
152
+ skipif-ci-or-mac = '("CI" in os_environ) or (sys_platform == "darwin")'
153
+ skipif-linux = 'sys_platform == "linux"'
154
+ skipif-mac = 'sys_platform == "darwin"'
155
+ skipif-not-linux = 'sys_platform != "linux"'
156
+ skipif-not-macos = 'sys_platform != "darwin"'
157
+
158
+ [tool.coverage.html]
159
+ directory = ".coverage/html"
160
+
161
+ [tool.coverage.report]
162
+ exclude_also = [
163
+ "@overload",
164
+ "assert_never",
165
+ "case never:",
166
+ "if TYPE_CHECKING:",
167
+ ]
168
+ fail_under = 100.0
169
+ skip_covered = true
170
+ skip_empty = true
171
+
172
+ [tool.coverage.run]
173
+ branch = true
174
+ data_file = ".coverage/data"
175
+ omit = [
176
+ "src/utilities/__init__.py",
177
+ "src/utilities/pytest_plugins/*.py",
178
+ "src/utilities/streamlit.py",
179
+ ]
180
+ parallel = true
181
+ plugins = ["coverage_conditional_plugin"]
182
+ source = ["src/utilities"]
183
+
184
+ # pyright
185
+ [tool.pyright]
186
+ deprecateTypingAliases = true
187
+ enableReachabilityAnalysis = false
188
+ ignore = ["**/_typeshed/**"]
189
+ pythonVersion = "3.12"
190
+ reportCallInDefaultInitializer = true
191
+ reportImplicitOverride = true
192
+ reportImplicitStringConcatenation = true
193
+ reportImportCycles = true
194
+ reportMissingSuperCall = true
195
+ reportMissingTypeArgument = false
196
+ reportMissingTypeStubs = false
197
+ reportPrivateUsage = false
198
+ reportPropertyTypeMismatch = true
199
+ reportUninitializedInstanceVariable = true
200
+ reportUnknownArgumentType = false
201
+ reportUnknownMemberType = false
202
+ reportUnknownParameterType = false
203
+ reportUnknownVariableType = false
204
+ reportUnnecessaryComparison = false
205
+ reportUnnecessaryTypeIgnoreComment = true
206
+ reportUnusedCallResult = true
207
+ reportUnusedImport = false
208
+ reportUnusedVariable = false
209
+ typeCheckingMode = "strict"
210
+
211
+ # pytest
212
+ [tool.pytest]
213
+ addopts = [
214
+ "-ra",
215
+ "-vv",
216
+ "--color=auto",
217
+ "--durations=10",
218
+ "--durations-min=10",
219
+ "--timeout=600",
220
+ ]
221
+ asyncio_default_fixture_loop_scope = "function"
222
+ asyncio_mode = "auto"
223
+ collect_imported_tests = false
224
+ empty_parameter_set_mark = "fail_at_collect"
225
+ filterwarnings = [
226
+ "error",
227
+ "ignore:<aiosqlite.*core.*Connection .*> was delete before being closed:ResourceWarning", # sqlalchemy
228
+ "ignore:Exception ignored in.* <coroutine object .* at .*>:pytest.PytestUnraisableExceptionWarning",
229
+ "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning",
230
+ "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter
231
+ "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning",
232
+ "ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)>:RuntimeWarning", # sqlalchemy
233
+ "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
234
+ "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
235
+ "ignore:loop is closed:ResourceWarning", # redis
236
+ "ignore:unclosed <StreamWriter .*>:ResourceWarning", # redis
237
+ "ignore:unclosed <socket.*socket .*>:ResourceWarning", # redis
238
+ "ignore:unclosed Connection <redis.*asyncio.*connection.*Connection.*>:ResourceWarning", # redis
239
+ "ignore:unclosed connection <asyncpg.*connection.*Connection.*>:ResourceWarning", # asyncpg
240
+ "ignore:unclosed database in <sqlite3.*Connection .*>:ResourceWarning", # sqlalchemy
241
+ "ignore:unclosed event loop <_UnixSelectorEventLoop .*>:ResourceWarning", # redis
242
+ "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging
243
+ "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis
244
+ "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest
245
+ ]
246
+ minversion = "9.0"
247
+ strict = true
248
+ testpaths = ["src/tests"]
249
+ timeout = "600"
250
+ xfail_strict = true
251
+
252
+ # ruff
253
+ [tool.ruff]
254
+ src = ["src"]
255
+ target-version = "py312"
256
+ unsafe-fixes = true
257
+
258
+ [tool.ruff.format]
259
+ preview = true
260
+ skip-magic-trailing-comma = true
261
+
262
+ [tool.ruff.lint]
263
+ explicit-preview-rules = true
264
+ fixable = ["ALL"]
265
+ ignore = [
266
+ "ANN401", # any-type
267
+ "A005", # stdlib-module-shadowing
268
+ "ASYNC109", # async-function-with-timeout
269
+ "C901", # complex-structure
270
+ "CPY", # flake8-copyright
271
+ "D", # pydocstyle
272
+ "DOC", # pydoclint
273
+ "E501", # line-too-long
274
+ "PD", # pandas-vet
275
+ "PERF203", # try-except-in-loop
276
+ "PLC0415", # import-outside-top-level
277
+ "PLR0911", # too-many-return-statements
278
+ "PLR0912", # too-many-branches
279
+ "PLR0913", # too-many-arguments
280
+ "PLR0915", # too-many-statements
281
+ "PLR2004", # magic-value-comparison
282
+ "PT012", # pytest-raises-with-multiple-statements
283
+ "PT013", # pytest-incorrect-pytest-import
284
+ "S202", # tarfile-unsafe-members
285
+ "S310", # suspicious-url-open-usage
286
+ "S311", # suspicious-non-cryptographic-random-usage
287
+ "S602", # subprocess-popen-with-shell-equals-true
288
+ "S603", # subprocess-without-shell-equals-true
289
+ "S607", # start-process-with-partial-path
290
+ # preview
291
+ "S101", # assert
292
+ # formatter
293
+ "W191", # tab-indentation
294
+ "E111", # indentation-with-invalid-multiple
295
+ "E114", # indentation-with-invalid-multiple-comment
296
+ "E117", # over-indented
297
+ "COM812", # missing-trailing-comma
298
+ "COM819", # prohibited-trailing-comma
299
+ "ISC001", # single-line-implicit-string-concatenation
300
+ "ISC002", # multi-line-implicit-string-concatenation
301
+ ]
302
+ preview = true
303
+ select = [
304
+ "ALL",
305
+ "RUF022", # unsorted-dunder-all
306
+ ]
307
+
308
+ [tool.ruff.lint.extend-per-file-ignores]
309
+ "src/tests/**/*.py" = [
310
+ "S101", # assert
311
+ "SLF001", # private-member-access
312
+ ]
313
+ "src/tests/test_typing_funcs/no_future.py" = [
314
+ "I002",
315
+ ] # missing-required-import
316
+
317
+ [tool.ruff.lint.flake8-tidy-imports]
318
+ ban-relative-imports = "all"
319
+
320
+ [tool.ruff.lint.isort]
321
+ required-imports = ["from __future__ import annotations"]
322
+ split-on-trailing-comma = false
323
+
324
+ # uv
325
+ [tool.uv]
326
+ default-groups = "all"
327
+
328
+ [tool.uv.build-backend]
329
+ module-name = "utilities"
330
+ module-root = "src"
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.166.12"
3
+ __version__ = "0.173.4"
@@ -169,7 +169,8 @@ class LiftedEvent[F: Callable[..., MaybeCoro[None]]]:
169
169
  def __await__(self) -> Any:
170
170
  return self.event.__await__() # pragma: no cover
171
171
 
172
- __aiter__ = aiter
172
+ def __aiter__(self) -> Any:
173
+ return self.event.aiter() # pragma: no cover
173
174
 
174
175
  def __contains__(self, c: Any, /) -> bool:
175
176
  return self.event.__contains__(c) # pragma: no cover
@@ -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: Sequence[str]
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.mark_line(interpolate=interpolate)
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.transform_pivot(var_name, value=value_name, groupby=[x_use])
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,
@@ -229,7 +229,8 @@ def plot_intraday_dataframe(
229
229
  )
230
230
 
231
231
  data4 = (
232
- data3.group_by("_date_index")
232
+ data3
233
+ .group_by("_date_index")
233
234
  .agg(
234
235
  col(f"_{datetime}_index").min().alias(f"{datetime}_index_min"),
235
236
  (col(f"_{datetime}_index").max() + 1).alias(f"{datetime}_index_max"),
@@ -31,6 +31,7 @@ from typing import (
31
31
  Self,
32
32
  TextIO,
33
33
  assert_never,
34
+ cast,
34
35
  overload,
35
36
  override,
36
37
  )
@@ -38,6 +39,7 @@ from typing import (
38
39
  from utilities.functions import ensure_int, ensure_not_none
39
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
42
44
  from utilities.shelve import yield_shelf
43
45
  from utilities.text import to_bool
@@ -48,6 +50,7 @@ if TYPE_CHECKING:
48
50
  from asyncio import _CoroutineLike
49
51
  from asyncio.subprocess import Process
50
52
  from collections.abc import (
53
+ AsyncIterable,
51
54
  AsyncIterator,
52
55
  Callable,
53
56
  ItemsView,
@@ -346,6 +349,24 @@ class EnhancedTaskGroup(TaskGroup):
346
349
  ##
347
350
 
348
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
+
349
370
  def get_coroutine_name(func: Callable[[], Coro[Any]], /) -> str:
350
371
  """Get the name of a coroutine, and then dispose of it gracefully."""
351
372
  coro = func()
@@ -394,6 +415,43 @@ def get_items_nowait[T](queue: Queue[T], /, *, max_size: int | None = None) -> l
394
415
  ##
395
416
 
396
417
 
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"
450
+
451
+
452
+ ##
453
+
454
+
397
455
  async def put_items[T](items: Iterable[T], queue: Queue[T], /) -> None:
398
456
  """Put items into a queue; if full then wait."""
399
457
  for item in items:
@@ -455,27 +513,21 @@ class StreamCommandOutput:
455
513
 
456
514
  @property
457
515
  def return_code(self) -> int:
458
- return ensure_int(self.process.returncode) # skipif-not-windows
516
+ return ensure_int(self.process.returncode)
459
517
 
460
518
 
461
519
  async def stream_command(cmd: str, /) -> StreamCommandOutput:
462
520
  """Run a shell command asynchronously and stream its output in real time."""
463
- process = await create_subprocess_shell( # skipif-not-windows
464
- cmd, stdout=PIPE, stderr=PIPE
465
- )
466
- proc_stdout = ensure_not_none( # skipif-not-windows
467
- process.stdout, desc="process.stdout"
468
- )
469
- proc_stderr = ensure_not_none( # skipif-not-windows
470
- process.stderr, desc="process.stderr"
471
- )
472
- ret_stdout = StringIO() # skipif-not-windows
473
- ret_stderr = StringIO() # skipif-not-windows
474
- 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:
475
527
  _ = tg.create_task(_stream_one(proc_stdout, stdout, ret_stdout))
476
528
  _ = tg.create_task(_stream_one(proc_stderr, stderr, ret_stderr))
477
- _ = await process.wait() # skipif-not-windows
478
- return StreamCommandOutput( # skipif-not-windows
529
+ _ = await process.wait()
530
+ return StreamCommandOutput(
479
531
  process=process, stdout=ret_stdout.getvalue(), stderr=ret_stderr.getvalue()
480
532
  )
481
533
 
@@ -484,7 +536,7 @@ async def _stream_one(
484
536
  input_: StreamReader, out_stream: TextIO, ret_stream: StringIO, /
485
537
  ) -> None:
486
538
  """Asynchronously read from a stream and write to the target output stream."""
487
- while True: # skipif-not-windows
539
+ while True:
488
540
  line = await input_.readline()
489
541
  if not line:
490
542
  break
@@ -542,10 +594,15 @@ async def yield_locked_shelf(
542
594
  __all__ = [
543
595
  "AsyncDict",
544
596
  "EnhancedTaskGroup",
597
+ "OneAsyncEmptyError",
598
+ "OneAsyncError",
599
+ "OneAsyncNonUniqueError",
545
600
  "StreamCommandOutput",
601
+ "chain_async",
546
602
  "get_coroutine_name",
547
603
  "get_items",
548
604
  "get_items_nowait",
605
+ "one_async",
549
606
  "put_items",
550
607
  "put_items_nowait",
551
608
  "sleep_max",
@@ -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 ttl_cache(
104
- maxsize=inf if max_size is None else max_size,
105
- ttl=inf if max_duration is None else max_duration.in_seconds(),
106
- timer=timer,
107
- typed=typed_,
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