dycw-utilities 0.166.18__tar.gz → 0.174.19__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.
Files changed (229) hide show
  1. dycw_utilities-0.174.19/PKG-INFO +41 -0
  2. dycw_utilities-0.174.19/pyproject.toml +332 -0
  3. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/__init__.py +1 -1
  4. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/aeventkit.py +2 -1
  5. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/altair.py +6 -3
  6. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/asyncio.py +73 -16
  7. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/cachetools.py +9 -6
  8. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/click.py +76 -20
  9. dycw_utilities-0.174.19/src/utilities/docker.py +293 -0
  10. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/functions.py +1 -1
  11. dycw_utilities-0.174.19/src/utilities/grp.py +28 -0
  12. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/hypothesis.py +39 -7
  13. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/iterables.py +1 -1
  14. dycw_utilities-0.174.19/src/utilities/jinja2.py +148 -0
  15. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/logging.py +7 -9
  16. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/orjson.py +18 -18
  17. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/os.py +39 -1
  18. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/parse.py +2 -2
  19. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pathlib.py +18 -1
  20. dycw_utilities-0.174.19/src/utilities/permissions.py +298 -0
  21. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/platform.py +1 -1
  22. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/polars.py +4 -1
  23. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/postgres.py +28 -29
  24. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pottery.py +2 -2
  25. dycw_utilities-0.174.19/src/utilities/pwd.py +28 -0
  26. dycw_utilities-0.174.19/src/utilities/pydantic.py +11 -0
  27. dycw_utilities-0.174.19/src/utilities/pydantic_settings.py +240 -0
  28. dycw_utilities-0.174.19/src/utilities/pydantic_settings_sops.py +76 -0
  29. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pytest.py +149 -31
  30. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pytest_plugins/pytest_regressions.py +6 -2
  31. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pytest_regressions.py +1 -2
  32. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/redis.py +2 -2
  33. dycw_utilities-0.174.19/src/utilities/shutil.py +25 -0
  34. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/sqlalchemy.py +15 -0
  35. dycw_utilities-0.174.19/src/utilities/subprocess.py +1253 -0
  36. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/tempfile.py +36 -1
  37. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/text.py +48 -32
  38. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/timer.py +2 -2
  39. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/traceback.py +1 -1
  40. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/types.py +5 -0
  41. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/typing.py +8 -2
  42. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/whenever.py +36 -5
  43. dycw_utilities-0.166.18/.gitignore +0 -169
  44. dycw_utilities-0.166.18/LICENSE +0 -21
  45. dycw_utilities-0.166.18/PKG-INFO +0 -41
  46. dycw_utilities-0.166.18/pyproject.toml +0 -498
  47. dycw_utilities-0.166.18/src/tests/__init__.py +0 -0
  48. dycw_utilities-0.166.18/src/tests/conftest.py +0 -193
  49. dycw_utilities-0.166.18/src/tests/modules/package_missing/__init__.py +0 -1
  50. dycw_utilities-0.166.18/src/tests/modules/package_missing/module.py +0 -5
  51. dycw_utilities-0.166.18/src/tests/modules/package_with/__init__.py +0 -1
  52. dycw_utilities-0.166.18/src/tests/modules/package_with/outer_1.py +0 -17
  53. dycw_utilities-0.166.18/src/tests/modules/package_with/outer_2.py +0 -17
  54. dycw_utilities-0.166.18/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  55. dycw_utilities-0.166.18/src/tests/modules/package_with/subpackage/inner_1.py +0 -17
  56. dycw_utilities-0.166.18/src/tests/modules/package_with/subpackage/inner_2.py +0 -17
  57. dycw_utilities-0.166.18/src/tests/modules/package_with/subpackage/inner_3.py +0 -17
  58. dycw_utilities-0.166.18/src/tests/modules/package_without/__init__.py +0 -1
  59. dycw_utilities-0.166.18/src/tests/modules/package_without/module_1.py +0 -17
  60. dycw_utilities-0.166.18/src/tests/modules/package_without/module_2.py +0 -17
  61. dycw_utilities-0.166.18/src/tests/modules/standalone.py +0 -17
  62. dycw_utilities-0.166.18/src/tests/modules/with_imports.py +0 -7
  63. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -5
  64. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -71
  65. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -5
  66. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -5
  67. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -5
  68. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -9
  69. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -74
  70. dycw_utilities-0.166.18/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -71
  71. dycw_utilities-0.166.18/src/tests/test_aeventkit.py +0 -282
  72. dycw_utilities-0.166.18/src/tests/test_altair.py +0 -169
  73. dycw_utilities-0.166.18/src/tests/test_asyncio.py +0 -433
  74. dycw_utilities-0.166.18/src/tests/test_atomicwrites.py +0 -198
  75. dycw_utilities-0.166.18/src/tests/test_atools.py +0 -67
  76. dycw_utilities-0.166.18/src/tests/test_cachetools.py +0 -85
  77. dycw_utilities-0.166.18/src/tests/test_click.py +0 -439
  78. dycw_utilities-0.166.18/src/tests/test_concurrent.py +0 -86
  79. dycw_utilities-0.166.18/src/tests/test_contextlib.py +0 -242
  80. dycw_utilities-0.166.18/src/tests/test_contextvars.py +0 -37
  81. dycw_utilities-0.166.18/src/tests/test_cryptography.py +0 -38
  82. dycw_utilities-0.166.18/src/tests/test_cvxpy.py +0 -649
  83. dycw_utilities-0.166.18/src/tests/test_dataclasses.py +0 -1012
  84. dycw_utilities-0.166.18/src/tests/test_enum.py +0 -222
  85. dycw_utilities-0.166.18/src/tests/test_errors.py +0 -82
  86. dycw_utilities-0.166.18/src/tests/test_fastapi.py +0 -43
  87. dycw_utilities-0.166.18/src/tests/test_fpdf2.py +0 -22
  88. dycw_utilities-0.166.18/src/tests/test_functions.py +0 -778
  89. dycw_utilities-0.166.18/src/tests/test_functools.py +0 -59
  90. dycw_utilities-0.166.18/src/tests/test_getpass.py +0 -8
  91. dycw_utilities-0.166.18/src/tests/test_git.py +0 -19
  92. dycw_utilities-0.166.18/src/tests/test_gzip.py +0 -21
  93. dycw_utilities-0.166.18/src/tests/test_hashlib.py +0 -14
  94. dycw_utilities-0.166.18/src/tests/test_http.py +0 -20
  95. dycw_utilities-0.166.18/src/tests/test_hypothesis.py +0 -1273
  96. dycw_utilities-0.166.18/src/tests/test_importlib.py +0 -22
  97. dycw_utilities-0.166.18/src/tests/test_inflect.py +0 -29
  98. dycw_utilities-0.166.18/src/tests/test_ipython.py +0 -15
  99. dycw_utilities-0.166.18/src/tests/test_iterables.py +0 -1394
  100. dycw_utilities-0.166.18/src/tests/test_json.py +0 -52
  101. dycw_utilities-0.166.18/src/tests/test_jupyter.py +0 -64
  102. dycw_utilities-0.166.18/src/tests/test_libcst.py +0 -168
  103. dycw_utilities-0.166.18/src/tests/test_lightweight_charts.py +0 -64
  104. dycw_utilities-0.166.18/src/tests/test_logging.py +0 -534
  105. dycw_utilities-0.166.18/src/tests/test_math.py +0 -1383
  106. dycw_utilities-0.166.18/src/tests/test_memory_profiler.py +0 -21
  107. dycw_utilities-0.166.18/src/tests/test_modules.py +0 -144
  108. dycw_utilities-0.166.18/src/tests/test_more_itertools.py +0 -354
  109. dycw_utilities-0.166.18/src/tests/test_numpy.py +0 -1178
  110. dycw_utilities-0.166.18/src/tests/test_objects/__init__.py +0 -1
  111. dycw_utilities-0.166.18/src/tests/test_objects/objects.py +0 -225
  112. dycw_utilities-0.166.18/src/tests/test_operator.py +0 -127
  113. dycw_utilities-0.166.18/src/tests/test_optuna.py +0 -76
  114. dycw_utilities-0.166.18/src/tests/test_orjson.py +0 -799
  115. dycw_utilities-0.166.18/src/tests/test_os.py +0 -160
  116. dycw_utilities-0.166.18/src/tests/test_parse.py +0 -756
  117. dycw_utilities-0.166.18/src/tests/test_pathlib.py +0 -325
  118. dycw_utilities-0.166.18/src/tests/test_pickle.py +0 -21
  119. dycw_utilities-0.166.18/src/tests/test_platform.py +0 -97
  120. dycw_utilities-0.166.18/src/tests/test_polars.py +0 -3204
  121. dycw_utilities-0.166.18/src/tests/test_polars_ols.py +0 -110
  122. dycw_utilities-0.166.18/src/tests/test_postgres.py +0 -162
  123. dycw_utilities-0.166.18/src/tests/test_pottery.py +0 -103
  124. dycw_utilities-0.166.18/src/tests/test_pqdm.py +0 -105
  125. dycw_utilities-0.166.18/src/tests/test_psutil.py +0 -24
  126. dycw_utilities-0.166.18/src/tests/test_pydantic_settings.py +0 -174
  127. dycw_utilities-0.166.18/src/tests/test_pydantic_settings_sops.py +0 -50
  128. dycw_utilities-0.166.18/src/tests/test_pyinstrument.py +0 -19
  129. dycw_utilities-0.166.18/src/tests/test_pytest.py +0 -434
  130. dycw_utilities-0.166.18/src/tests/test_pytest_randomly.py +0 -8
  131. dycw_utilities-0.166.18/src/tests/test_pytest_regressions.py +0 -68
  132. dycw_utilities-0.166.18/src/tests/test_random.py +0 -85
  133. dycw_utilities-0.166.18/src/tests/test_re.py +0 -129
  134. dycw_utilities-0.166.18/src/tests/test_redis.py +0 -768
  135. dycw_utilities-0.166.18/src/tests/test_reprlib.py +0 -87
  136. dycw_utilities-0.166.18/src/tests/test_scipy.py +0 -48
  137. dycw_utilities-0.166.18/src/tests/test_sentinel.py +0 -53
  138. dycw_utilities-0.166.18/src/tests/test_shelve.py +0 -26
  139. dycw_utilities-0.166.18/src/tests/test_slack_sdk.py +0 -34
  140. dycw_utilities-0.166.18/src/tests/test_socket.py +0 -8
  141. dycw_utilities-0.166.18/src/tests/test_sqlalchemy.py +0 -1452
  142. dycw_utilities-0.166.18/src/tests/test_sqlalchemy_polars.py +0 -638
  143. dycw_utilities-0.166.18/src/tests/test_statsmodels.py +0 -51
  144. dycw_utilities-0.166.18/src/tests/test_string.py +0 -51
  145. dycw_utilities-0.166.18/src/tests/test_tempfile.py +0 -31
  146. dycw_utilities-0.166.18/src/tests/test_testbook.py +0 -38
  147. dycw_utilities-0.166.18/src/tests/test_text.py +0 -377
  148. dycw_utilities-0.166.18/src/tests/test_threading.py +0 -42
  149. dycw_utilities-0.166.18/src/tests/test_timer.py +0 -130
  150. dycw_utilities-0.166.18/src/tests/test_traceback.py +0 -168
  151. dycw_utilities-0.166.18/src/tests/test_typed_settings.py +0 -321
  152. dycw_utilities-0.166.18/src/tests/test_types.py +0 -53
  153. dycw_utilities-0.166.18/src/tests/test_typing.py +0 -1166
  154. dycw_utilities-0.166.18/src/tests/test_typing_funcs/__init__.py +0 -0
  155. dycw_utilities-0.166.18/src/tests/test_typing_funcs/no_future.py +0 -45
  156. dycw_utilities-0.166.18/src/tests/test_typing_funcs/with_future.py +0 -246
  157. dycw_utilities-0.166.18/src/tests/test_tzdata.py +0 -14
  158. dycw_utilities-0.166.18/src/tests/test_tzlocal.py +0 -17
  159. dycw_utilities-0.166.18/src/tests/test_uuid.py +0 -96
  160. dycw_utilities-0.166.18/src/tests/test_version.py +0 -142
  161. dycw_utilities-0.166.18/src/tests/test_warnings.py +0 -74
  162. dycw_utilities-0.166.18/src/tests/test_whenever.py +0 -1805
  163. dycw_utilities-0.166.18/src/tests/test_zipfile.py +0 -37
  164. dycw_utilities-0.166.18/src/tests/test_zoneinfo.py +0 -131
  165. dycw_utilities-0.166.18/src/utilities/pydantic_settings.py +0 -180
  166. dycw_utilities-0.166.18/src/utilities/pydantic_settings_sops.py +0 -39
  167. dycw_utilities-0.166.18/src/utilities/pytest_plugins/__init__.py +0 -1
  168. dycw_utilities-0.166.18/src/utilities/typed_settings.py +0 -152
  169. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/README.md +0 -0
  170. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/atomicwrites.py +0 -0
  171. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/atools.py +0 -0
  172. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/concurrent.py +0 -0
  173. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/contextlib.py +0 -0
  174. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/contextvars.py +0 -0
  175. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/cryptography.py +0 -0
  176. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/cvxpy.py +0 -0
  177. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/dataclasses.py +0 -0
  178. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/enum.py +0 -0
  179. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/errors.py +0 -0
  180. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/fastapi.py +0 -0
  181. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/fpdf2.py +0 -0
  182. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/functools.py +0 -0
  183. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/getpass.py +0 -0
  184. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/git.py +0 -0
  185. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/gzip.py +0 -0
  186. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/hashlib.py +0 -0
  187. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/http.py +0 -0
  188. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/importlib.py +0 -0
  189. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/inflect.py +0 -0
  190. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/ipython.py +0 -0
  191. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/json.py +0 -0
  192. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/jupyter.py +0 -0
  193. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/libcst.py +0 -0
  194. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/lightweight_charts.py +0 -0
  195. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/math.py +0 -0
  196. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/memory_profiler.py +0 -0
  197. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/modules.py +0 -0
  198. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/more_itertools.py +0 -0
  199. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/numpy.py +0 -0
  200. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/operator.py +0 -0
  201. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/optuna.py +0 -0
  202. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pickle.py +0 -0
  203. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/polars_ols.py +0 -0
  204. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pqdm.py +0 -0
  205. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/psutil.py +0 -0
  206. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/py.typed +0 -0
  207. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pyinstrument.py +0 -0
  208. {dycw_utilities-0.166.18/src/tests/modules → dycw_utilities-0.174.19/src/utilities/pytest_plugins}/__init__.py +0 -0
  209. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  210. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/random.py +0 -0
  211. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/re.py +0 -0
  212. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/reprlib.py +0 -0
  213. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/scipy.py +0 -0
  214. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/sentinel.py +0 -0
  215. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/shelve.py +0 -0
  216. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/slack_sdk.py +0 -0
  217. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/socket.py +0 -0
  218. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/sqlalchemy_polars.py +0 -0
  219. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/statsmodels.py +0 -0
  220. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/string.py +0 -0
  221. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/testbook.py +0 -0
  222. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/threading.py +0 -0
  223. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/tzdata.py +0 -0
  224. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/tzlocal.py +0 -0
  225. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/uuid.py +0 -0
  226. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/version.py +0 -0
  227. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/warnings.py +0 -0
  228. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/zipfile.py +0 -0
  229. {dycw_utilities-0.166.18 → dycw_utilities-0.174.19}/src/utilities/zoneinfo.py +0 -0
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.3
2
+ Name: dycw-utilities
3
+ Version: 0.174.19
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,332 @@
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.128.0, <0.129"]
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.174.19"
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.174.19"
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
+ skipif-not-windows = 'sys_platform != "windows"'
158
+ skipif-windows = 'sys_platform == "darwin"'
159
+
160
+ [tool.coverage.html]
161
+ directory = ".coverage/html"
162
+
163
+ [tool.coverage.report]
164
+ exclude_also = [
165
+ "@overload",
166
+ "assert_never",
167
+ "case never:",
168
+ "if TYPE_CHECKING:",
169
+ ]
170
+ fail_under = 100.0
171
+ skip_covered = true
172
+ skip_empty = true
173
+
174
+ [tool.coverage.run]
175
+ branch = true
176
+ data_file = ".coverage/data"
177
+ omit = [
178
+ "src/utilities/__init__.py",
179
+ "src/utilities/pytest_plugins/*.py",
180
+ "src/utilities/streamlit.py",
181
+ ]
182
+ parallel = true
183
+ plugins = ["coverage_conditional_plugin"]
184
+ source = ["src/utilities"]
185
+
186
+ # pyright
187
+ [tool.pyright]
188
+ deprecateTypingAliases = true
189
+ enableReachabilityAnalysis = false
190
+ ignore = ["**/_typeshed/**"]
191
+ pythonVersion = "3.12"
192
+ reportCallInDefaultInitializer = true
193
+ reportImplicitOverride = true
194
+ reportImplicitStringConcatenation = true
195
+ reportImportCycles = true
196
+ reportMissingSuperCall = true
197
+ reportMissingTypeArgument = false
198
+ reportMissingTypeStubs = false
199
+ reportPrivateUsage = false
200
+ reportPropertyTypeMismatch = true
201
+ reportUninitializedInstanceVariable = true
202
+ reportUnknownArgumentType = false
203
+ reportUnknownMemberType = false
204
+ reportUnknownParameterType = false
205
+ reportUnknownVariableType = false
206
+ reportUnnecessaryComparison = false
207
+ reportUnnecessaryTypeIgnoreComment = true
208
+ reportUnusedCallResult = true
209
+ reportUnusedImport = false
210
+ reportUnusedVariable = false
211
+ typeCheckingMode = "strict"
212
+
213
+ # pytest
214
+ [tool.pytest]
215
+ addopts = [
216
+ "-ra",
217
+ "-vv",
218
+ "--color=auto",
219
+ "--durations=10",
220
+ "--durations-min=10",
221
+ "--timeout=600",
222
+ ]
223
+ asyncio_default_fixture_loop_scope = "function"
224
+ asyncio_mode = "auto"
225
+ collect_imported_tests = false
226
+ empty_parameter_set_mark = "fail_at_collect"
227
+ filterwarnings = [
228
+ "error",
229
+ "ignore:<aiosqlite.*core.*Connection .*> was delete before being closed:ResourceWarning", # sqlalchemy
230
+ "ignore:Exception ignored in.* <coroutine object .* at .*>:pytest.PytestUnraisableExceptionWarning",
231
+ "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning",
232
+ "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter
233
+ "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning",
234
+ "ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)>:RuntimeWarning", # sqlalchemy
235
+ "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
236
+ "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
237
+ "ignore:loop is closed:ResourceWarning", # redis
238
+ "ignore:unclosed <StreamWriter .*>:ResourceWarning", # redis
239
+ "ignore:unclosed <socket.*socket .*>:ResourceWarning", # redis
240
+ "ignore:unclosed Connection <redis.*asyncio.*connection.*Connection.*>:ResourceWarning", # redis
241
+ "ignore:unclosed connection <asyncpg.*connection.*Connection.*>:ResourceWarning", # asyncpg
242
+ "ignore:unclosed database in <sqlite3.*Connection .*>:ResourceWarning", # sqlalchemy
243
+ "ignore:unclosed event loop <_UnixSelectorEventLoop .*>:ResourceWarning", # redis
244
+ "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging
245
+ "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis
246
+ "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest
247
+ ]
248
+ minversion = "9.0"
249
+ strict = true
250
+ testpaths = ["src/tests"]
251
+ timeout = "600"
252
+ xfail_strict = true
253
+
254
+ # ruff
255
+ [tool.ruff]
256
+ src = ["src"]
257
+ target-version = "py312"
258
+ unsafe-fixes = true
259
+
260
+ [tool.ruff.format]
261
+ preview = true
262
+ skip-magic-trailing-comma = true
263
+
264
+ [tool.ruff.lint]
265
+ explicit-preview-rules = true
266
+ fixable = ["ALL"]
267
+ ignore = [
268
+ "ANN401", # any-type
269
+ "A005", # stdlib-module-shadowing
270
+ "ASYNC109", # async-function-with-timeout
271
+ "C901", # complex-structure
272
+ "CPY", # flake8-copyright
273
+ "D", # pydocstyle
274
+ "DOC", # pydoclint
275
+ "E501", # line-too-long
276
+ "PD", # pandas-vet
277
+ "PERF203", # try-except-in-loop
278
+ "PLC0415", # import-outside-top-level
279
+ "PLR0911", # too-many-return-statements
280
+ "PLR0912", # too-many-branches
281
+ "PLR0913", # too-many-arguments
282
+ "PLR0915", # too-many-statements
283
+ "PLR2004", # magic-value-comparison
284
+ "PT012", # pytest-raises-with-multiple-statements
285
+ "PT013", # pytest-incorrect-pytest-import
286
+ "S202", # tarfile-unsafe-members
287
+ "S310", # suspicious-url-open-usage
288
+ "S311", # suspicious-non-cryptographic-random-usage
289
+ "S602", # subprocess-popen-with-shell-equals-true
290
+ "S603", # subprocess-without-shell-equals-true
291
+ "S607", # start-process-with-partial-path
292
+ # preview
293
+ "S101", # assert
294
+ # formatter
295
+ "W191", # tab-indentation
296
+ "E111", # indentation-with-invalid-multiple
297
+ "E114", # indentation-with-invalid-multiple-comment
298
+ "E117", # over-indented
299
+ "COM812", # missing-trailing-comma
300
+ "COM819", # prohibited-trailing-comma
301
+ "ISC001", # single-line-implicit-string-concatenation
302
+ "ISC002", # multi-line-implicit-string-concatenation
303
+ ]
304
+ preview = true
305
+ select = [
306
+ "ALL",
307
+ "RUF022", # unsorted-dunder-all
308
+ ]
309
+
310
+ [tool.ruff.lint.extend-per-file-ignores]
311
+ "src/tests/**/*.py" = [
312
+ "S101", # assert
313
+ "SLF001", # private-member-access
314
+ ]
315
+ "src/tests/test_typing_funcs/no_future.py" = [
316
+ "I002",
317
+ ] # missing-required-import
318
+
319
+ [tool.ruff.lint.flake8-tidy-imports]
320
+ ban-relative-imports = "all"
321
+
322
+ [tool.ruff.lint.isort]
323
+ required-imports = ["from __future__ import annotations"]
324
+ split-on-trailing-comma = false
325
+
326
+ # uv
327
+ [tool.uv]
328
+ default-groups = "all"
329
+
330
+ [tool.uv.build-backend]
331
+ module-name = "utilities"
332
+ module-root = "src"
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.166.18"
3
+ __version__ = "0.174.19"
@@ -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
@@ -94,7 +94,8 @@ def plot_dataframes(
94
94
  # lines
95
95
  selection = selection_point(bind="legend", fields=[var_name], nearest=False)
96
96
  lines = [
97
- chart.mark_line(interpolate=interpolate)
97
+ chart
98
+ .mark_line(interpolate=interpolate)
98
99
  .encode(
99
100
  x=x_use,
100
101
  y=Y(value_name).scale(zero=False),
@@ -124,7 +125,8 @@ def plot_dataframes(
124
125
  else:
125
126
  tooltip_format_use = Undefined
126
127
  rules = [
127
- chart.transform_pivot(var_name, value=value_name, groupby=[x_use])
128
+ chart
129
+ .transform_pivot(var_name, value=value_name, groupby=[x_use])
128
130
  .mark_rule(color="gray")
129
131
  .encode(
130
132
  x=x_use,
@@ -227,7 +229,8 @@ def plot_intraday_dataframe(
227
229
  )
228
230
 
229
231
  data4 = (
230
- data3.group_by("_date_index")
232
+ data3
233
+ .group_by("_date_index")
231
234
  .agg(
232
235
  col(f"_{datetime}_index").min().alias(f"{datetime}_index_min"),
233
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