dycw-utilities 0.155.4__tar.gz → 0.157.0__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 (213) hide show
  1. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/pyproject.toml +3 -3
  3. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_asyncio.py +2 -68
  4. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_click.py +14 -87
  5. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_eventkit.py +7 -3
  6. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_logging.py +12 -6
  7. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_os.py +6 -0
  8. dycw_utilities-0.157.0/src/tests/test_pottery.py +103 -0
  9. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pytest.py +1 -6
  10. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/__init__.py +1 -1
  11. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/asyncio.py +1 -45
  12. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/click.py +29 -15
  13. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/eventkit.py +5 -2
  14. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/logging.py +12 -4
  15. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/os.py +10 -1
  16. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pottery.py +6 -90
  17. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pytest.py +0 -9
  18. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/redis.py +1 -2
  19. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/sqlalchemy.py +1 -2
  20. dycw_utilities-0.155.4/src/tests/test_pottery.py +0 -200
  21. dycw_utilities-0.155.4/src/tests/test_yield_access/script.py +0 -61
  22. dycw_utilities-0.155.4/src/tests/test_yield_access/script.sh +0 -54
  23. dycw_utilities-0.155.4/src/utilities/pytest_plugins/__init__.py +0 -1
  24. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/.gitignore +0 -0
  25. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/LICENSE +0 -0
  26. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/README.md +0 -0
  27. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/__init__.py +0 -0
  28. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/conftest.py +0 -0
  29. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/__init__.py +0 -0
  30. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_missing/__init__.py +0 -0
  31. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_missing/module.py +0 -0
  32. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/__init__.py +0 -0
  33. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/outer_1.py +0 -0
  34. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/outer_2.py +0 -0
  35. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  36. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  37. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  38. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  39. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_without/__init__.py +0 -0
  40. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_without/module_1.py +0 -0
  41. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/package_without/module_2.py +0 -0
  42. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/standalone.py +0 -0
  43. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/modules/with_imports.py +0 -0
  44. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  45. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  46. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  47. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  48. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  49. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  50. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  51. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  52. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_altair.py +0 -0
  53. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_atomicwrites.py +0 -0
  54. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_atools.py +0 -0
  55. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_cachetools.py +0 -0
  56. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_concurrent.py +0 -0
  57. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_contextlib.py +0 -0
  58. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_contextvars.py +0 -0
  59. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_cryptography.py +0 -0
  60. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_cvxpy.py +0 -0
  61. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_dataclasses.py +0 -0
  62. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_enum.py +0 -0
  63. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_errors.py +0 -0
  64. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_fastapi.py +0 -0
  65. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_fpdf2.py +0 -0
  66. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_functions.py +0 -0
  67. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_functools.py +0 -0
  68. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_getpass.py +0 -0
  69. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_gzip.py +0 -0
  70. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_hashlib.py +0 -0
  71. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_http.py +0 -0
  72. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_hypothesis.py +0 -0
  73. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_importlib.py +0 -0
  74. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_inflect.py +0 -0
  75. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_ipython.py +0 -0
  76. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_iterables.py +0 -0
  77. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_json.py +0 -0
  78. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_jupyter.py +0 -0
  79. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_libcst.py +0 -0
  80. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_lightweight_charts.py +0 -0
  81. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_math.py +0 -0
  82. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_memory_profiler.py +0 -0
  83. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_modules.py +0 -0
  84. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_more_itertools.py +0 -0
  85. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_numpy.py +0 -0
  86. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_objects/__init__.py +0 -0
  87. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_objects/objects.py +0 -0
  88. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_operator.py +0 -0
  89. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_optuna.py +0 -0
  90. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_orjson.py +0 -0
  91. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_parse.py +0 -0
  92. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pathlib.py +0 -0
  93. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pickle.py +0 -0
  94. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_platform.py +0 -0
  95. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_polars.py +0 -0
  96. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_polars_ols.py +0 -0
  97. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_postgres.py +0 -0
  98. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pqdm.py +0 -0
  99. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_psutil.py +0 -0
  100. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pyinstrument.py +0 -0
  101. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pytest_randomly.py +0 -0
  102. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_pytest_regressions.py +0 -0
  103. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_random.py +0 -0
  104. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_re.py +0 -0
  105. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_redis.py +0 -0
  106. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_reprlib.py +0 -0
  107. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_scipy.py +0 -0
  108. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_sentinel.py +0 -0
  109. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_shelve.py +0 -0
  110. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_slack_sdk.py +0 -0
  111. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_socket.py +0 -0
  112. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_sqlalchemy.py +0 -0
  113. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  114. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_statsmodels.py +0 -0
  115. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_string.py +0 -0
  116. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_tempfile.py +0 -0
  117. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_text.py +0 -0
  118. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_threading.py +0 -0
  119. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_timer.py +0 -0
  120. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_traceback.py +0 -0
  121. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_typed_settings.py +0 -0
  122. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_types.py +0 -0
  123. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_typing.py +0 -0
  124. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  125. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  126. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  127. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_tzdata.py +0 -0
  128. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_tzlocal.py +0 -0
  129. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_uuid.py +0 -0
  130. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_version.py +0 -0
  131. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_warnings.py +0 -0
  132. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_whenever.py +0 -0
  133. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_zipfile.py +0 -0
  134. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/tests/test_zoneinfo.py +0 -0
  135. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/altair.py +0 -0
  136. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/atomicwrites.py +0 -0
  137. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/atools.py +0 -0
  138. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/cachetools.py +0 -0
  139. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/concurrent.py +0 -0
  140. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/contextlib.py +0 -0
  141. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/contextvars.py +0 -0
  142. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/cryptography.py +0 -0
  143. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/cvxpy.py +0 -0
  144. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/dataclasses.py +0 -0
  145. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/enum.py +0 -0
  146. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/errors.py +0 -0
  147. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/fastapi.py +0 -0
  148. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/fpdf2.py +0 -0
  149. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/functions.py +0 -0
  150. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/functools.py +0 -0
  151. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/getpass.py +0 -0
  152. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/gzip.py +0 -0
  153. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/hashlib.py +0 -0
  154. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/http.py +0 -0
  155. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/hypothesis.py +0 -0
  156. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/importlib.py +0 -0
  157. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/inflect.py +0 -0
  158. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/ipython.py +0 -0
  159. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/iterables.py +0 -0
  160. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/json.py +0 -0
  161. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/jupyter.py +0 -0
  162. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/libcst.py +0 -0
  163. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/lightweight_charts.py +0 -0
  164. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/math.py +0 -0
  165. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/memory_profiler.py +0 -0
  166. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/modules.py +0 -0
  167. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/more_itertools.py +0 -0
  168. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/numpy.py +0 -0
  169. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/operator.py +0 -0
  170. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/optuna.py +0 -0
  171. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/orjson.py +0 -0
  172. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/parse.py +0 -0
  173. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pathlib.py +0 -0
  174. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pickle.py +0 -0
  175. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/platform.py +0 -0
  176. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/polars.py +0 -0
  177. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/polars_ols.py +0 -0
  178. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/postgres.py +0 -0
  179. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pqdm.py +0 -0
  180. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/psutil.py +0 -0
  181. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/py.typed +0 -0
  182. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pyinstrument.py +0 -0
  183. {dycw_utilities-0.155.4/src/tests/test_yield_access → dycw_utilities-0.157.0/src/utilities/pytest_plugins}/__init__.py +0 -0
  184. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  185. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  186. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/pytest_regressions.py +0 -0
  187. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/random.py +0 -0
  188. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/re.py +0 -0
  189. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/reprlib.py +0 -0
  190. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/scipy.py +0 -0
  191. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/sentinel.py +0 -0
  192. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/shelve.py +0 -0
  193. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/slack_sdk.py +0 -0
  194. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/socket.py +0 -0
  195. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/sqlalchemy_polars.py +0 -0
  196. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/statsmodels.py +0 -0
  197. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/string.py +0 -0
  198. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/tempfile.py +0 -0
  199. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/text.py +0 -0
  200. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/threading.py +0 -0
  201. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/timer.py +0 -0
  202. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/traceback.py +0 -0
  203. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/typed_settings.py +0 -0
  204. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/types.py +0 -0
  205. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/typing.py +0 -0
  206. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/tzdata.py +0 -0
  207. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/tzlocal.py +0 -0
  208. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/uuid.py +0 -0
  209. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/version.py +0 -0
  210. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/warnings.py +0 -0
  211. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/whenever.py +0 -0
  212. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/zipfile.py +0 -0
  213. {dycw_utilities-0.155.4 → dycw_utilities-0.157.0}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.155.4
3
+ Version: 0.157.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -144,7 +144,7 @@ psutil = [
144
144
  "psutil >=7.0.0, <7.1",
145
145
  ]
146
146
  pyinstrument = [
147
- "pyinstrument >=5.0.3, <5.1",
147
+ "pyinstrument >=5.1.0, <5.2",
148
148
  ]
149
149
  pytest = [
150
150
  "pytest >=8.4.1, <8.5",
@@ -227,7 +227,7 @@ dependencies = [
227
227
  name = "dycw-utilities"
228
228
  readme = "README.md"
229
229
  requires-python = ">= 3.12"
230
- version = "0.155.4"
230
+ version = "0.157.0"
231
231
 
232
232
  [project.entry-points.pytest11]
233
233
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -259,7 +259,7 @@ test = [
259
259
  # bump-my-version
260
260
  [tool.bumpversion]
261
261
  allow_dirty = true
262
- current_version = "0.155.4"
262
+ current_version = "0.157.0"
263
263
 
264
264
  [[tool.bumpversion.files]]
265
265
  filename = "src/utilities/__init__.py"
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from asyncio import Queue, TaskGroup, run
3
+ from asyncio import Queue, run
4
4
  from collections.abc import ItemsView, KeysView, ValuesView
5
5
  from contextlib import asynccontextmanager
6
6
  from re import search
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, ClassVar
8
8
 
9
9
  from hypothesis import HealthCheck, given, settings
10
10
  from hypothesis.strategies import booleans, dictionaries, integers, lists, none
11
- from pytest import LogCaptureFixture, RaisesGroup, mark, param, raises
11
+ from pytest import RaisesGroup, raises
12
12
 
13
13
  from utilities.asyncio import (
14
14
  AsyncDict,
@@ -16,7 +16,6 @@ from utilities.asyncio import (
16
16
  get_coroutine_name,
17
17
  get_items,
18
18
  get_items_nowait,
19
- loop_until_succeed,
20
19
  put_items,
21
20
  put_items_nowait,
22
21
  sleep_max,
@@ -29,7 +28,6 @@ from utilities.asyncio import (
29
28
  )
30
29
  from utilities.hypothesis import pairs, text_ascii
31
30
  from utilities.pytest import skipif_windows
32
- from utilities.text import unique_str
33
31
  from utilities.timer import Timer
34
32
  from utilities.whenever import MILLISECOND, SECOND, get_now
35
33
 
@@ -319,70 +317,6 @@ class TestGetItems:
319
317
  assert result == xs[:max_size]
320
318
 
321
319
 
322
- class TestLoopUntilSucceed:
323
- @mark.parametrize("sleep", [param(MILLISECOND), param(None)])
324
- @mark.parametrize("use_logger", [param(True), param(False)])
325
- async def test_main(
326
- self, *, caplog: LogCaptureFixture, sleep: TimeDelta | None, use_logger: bool
327
- ) -> None:
328
- class CustomError(Exception): ...
329
-
330
- caplog.set_level("DEBUG", logger=(name := unique_str()))
331
- counter = 0
332
-
333
- async def func() -> None:
334
- nonlocal counter
335
- counter += 1
336
- if counter <= 3:
337
- raise CustomError
338
-
339
- assert await loop_until_succeed(
340
- lambda: func(), logger=name if use_logger else None, sleep=sleep
341
- )
342
- assert counter == 4
343
-
344
- if use_logger:
345
- messages = [r.message for r in caplog.records if r.name == name]
346
- expected = 3 * (
347
- ["Error running 'func'"]
348
- + ([] if sleep is None else ["Sleeping for PT0.001S..."])
349
- + ["Retrying 'func'..."]
350
- )
351
- assert messages == expected
352
-
353
- async def test_error_flat(self) -> None:
354
- class CustomError(Exception): ...
355
-
356
- counter = 0
357
-
358
- async def func() -> None:
359
- nonlocal counter
360
- counter += 1
361
- if counter <= 3:
362
- raise CustomError
363
-
364
- assert not await loop_until_succeed(lambda: func(), errors=CustomError)
365
- assert counter == 1
366
-
367
- async def test_error_nested(self) -> None:
368
- class CustomError(Exception): ...
369
-
370
- counter = 0
371
-
372
- async def func() -> None:
373
- async with TaskGroup() as tg:
374
- _ = tg.create_task(inner())
375
-
376
- async def inner() -> None:
377
- nonlocal counter
378
- counter += 1
379
- if counter <= 3:
380
- raise CustomError
381
-
382
- assert not await loop_until_succeed(lambda: func(), errors=CustomError)
383
- assert counter == 1
384
-
385
-
386
320
  class TestPutItems:
387
321
  @given(xs=lists(integers(), min_size=1), wait=booleans())
388
322
  async def test_main(self, *, xs: list[int], wait: bool) -> None:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import enum
4
+ import pathlib
4
5
  from dataclasses import dataclass
5
6
  from enum import auto
6
7
  from operator import attrgetter
@@ -30,11 +31,7 @@ from utilities.click import (
30
31
  Date,
31
32
  DateDelta,
32
33
  DateTimeDelta,
33
- DirPath,
34
34
  Enum,
35
- ExistingDirPath,
36
- ExistingFilePath,
37
- FilePath,
38
35
  FrozenSetChoices,
39
36
  FrozenSetEnums,
40
37
  FrozenSetInts,
@@ -46,6 +43,7 @@ from utilities.click import (
46
43
  ListInts,
47
44
  ListStrs,
48
45
  MonthDay,
46
+ Path,
49
47
  PlainDateTime,
50
48
  Time,
51
49
  TimeDelta,
@@ -57,6 +55,7 @@ from utilities.hypothesis import (
57
55
  date_time_deltas,
58
56
  dates,
59
57
  month_days,
58
+ paths,
60
59
  plain_date_times,
61
60
  text_ascii,
62
61
  time_deltas,
@@ -68,7 +67,6 @@ from utilities.text import join_strs, strip_and_dedent
68
67
 
69
68
  if TYPE_CHECKING:
70
69
  from collections.abc import Callable, Iterable
71
- from pathlib import Path
72
70
 
73
71
 
74
72
  class TestContextSettingsHelpOptionNames:
@@ -81,91 +79,17 @@ class TestContextSettingsHelpOptionNames:
81
79
  assert result.exit_code == 0
82
80
 
83
81
 
84
- class TestFileAndDirPaths:
85
- def test_existing_dir_path(self, *, tmp_path: Path) -> None:
86
- @command()
87
- @argument("path", type=ExistingDirPath)
88
- def cli(*, path: Path) -> None:
89
- from pathlib import Path
90
-
91
- assert isinstance(path, Path)
82
+ class TestPath:
83
+ def test_path(self, *, tmp_path: pathlib.Path) -> None:
84
+ path_use = pathlib.Path("~", tmp_path)
92
85
 
93
- result = CliRunner().invoke(cli, [str(tmp_path)])
94
- assert result.exit_code == 0
95
-
96
- file_path = tmp_path.joinpath("file.txt")
97
- file_path.touch()
98
- result = CliRunner().invoke(cli, [str(file_path)])
99
- assert result.exit_code == 2
100
- assert search("is a file", result.stderr)
101
-
102
- non_existent = tmp_path.joinpath("non-existent")
103
- result = CliRunner().invoke(cli, [str(non_existent)])
104
- assert result.exit_code == 2
105
- assert search("does not exist", result.stderr)
106
-
107
- def test_existing_file_path(self, *, tmp_path: Path) -> None:
108
86
  @command()
109
- @argument("path", type=ExistingFilePath)
110
- def cli(*, path: Path) -> None:
111
- from pathlib import Path
112
-
113
- assert isinstance(path, Path)
87
+ @argument("path", type=Path())
88
+ def cli(*, path: pathlib.Path) -> None:
89
+ assert isinstance(path, pathlib.Path)
90
+ assert path == path.expanduser()
114
91
 
115
- result = CliRunner().invoke(cli, [str(tmp_path)])
116
- assert result.exit_code == 2
117
- assert search("is a directory", result.stderr)
118
-
119
- file_path = tmp_path.joinpath("file.txt")
120
- file_path.touch()
121
- result = CliRunner().invoke(cli, [str(file_path)])
122
- assert result.exit_code == 0
123
-
124
- non_existent = tmp_path.joinpath("non-existent")
125
- result = CliRunner().invoke(cli, [str(non_existent)])
126
- assert result.exit_code == 2
127
- assert search("does not exist", result.stderr)
128
-
129
- def test_dir_path(self, *, tmp_path: Path) -> None:
130
- @command()
131
- @argument("path", type=DirPath)
132
- def cli(*, path: Path) -> None:
133
- from pathlib import Path
134
-
135
- assert isinstance(path, Path)
136
-
137
- result = CliRunner().invoke(cli, [str(tmp_path)])
138
- assert result.exit_code == 0
139
-
140
- file_path = tmp_path.joinpath("file.txt")
141
- file_path.touch()
142
- result = CliRunner().invoke(cli, [str(file_path)])
143
- assert result.exit_code == 2
144
- assert search("is a file", result.stderr)
145
-
146
- non_existent = tmp_path.joinpath("non-existent")
147
- result = CliRunner().invoke(cli, [str(non_existent)])
148
- assert result.exit_code == 0
149
-
150
- def test_file_path(self, *, tmp_path: Path) -> None:
151
- @command()
152
- @argument("path", type=FilePath)
153
- def cli(*, path: Path) -> None:
154
- from pathlib import Path
155
-
156
- assert isinstance(path, Path)
157
-
158
- result = CliRunner().invoke(cli, [str(tmp_path)])
159
- assert result.exit_code == 2
160
- assert search("is a directory", result.stderr)
161
-
162
- file_path = tmp_path.joinpath("file.txt")
163
- file_path.touch()
164
- result = CliRunner().invoke(cli, [str(file_path)])
165
- assert result.exit_code == 0
166
-
167
- non_existent = tmp_path.joinpath("non-existent")
168
- result = CliRunner().invoke(cli, [str(non_existent)])
92
+ result = CliRunner().invoke(cli, [str(path_use)])
169
93
  assert result.exit_code == 0
170
94
 
171
95
 
@@ -301,6 +225,9 @@ class TestParameters:
301
225
  serialize=whenever.MonthDay.format_common_iso,
302
226
  failable=True,
303
227
  ),
228
+ _Case(
229
+ param=Path(), name="path", strategy=paths(), serialize=str, failable=False
230
+ ),
304
231
  _Case(
305
232
  param=PlainDateTime(),
306
233
  name="plain date-time",
@@ -65,13 +65,17 @@ class TestAddListener:
65
65
 
66
66
  def listener_sync() -> None: ...
67
67
 
68
- _ = add_listener(event, listener_sync, logger=str(root))
68
+ _ = add_listener(
69
+ event, listener_sync, logger=str(root), logger_allow_pytest=True
70
+ )
69
71
  case "async":
70
72
 
71
73
  async def listener_async() -> None:
72
74
  await sleep(0.01)
73
75
 
74
- _ = add_listener(event, listener_async, logger=str(root))
76
+ _ = add_listener(
77
+ event, listener_async, logger=str(root), logger_allow_pytest=True
78
+ )
75
79
 
76
80
  event.emit(None)
77
81
  await sleep(0.01)
@@ -233,7 +237,7 @@ class TestLiftListener:
233
237
  LiftListenerError,
234
238
  match="Synchronous listener .* cannot be paired with an asynchronous error handler .*",
235
239
  ):
236
- _ = lift_listener(listener, Event(), error=error)
240
+ _ = lift_listener(listener, Event(), error=error, logger_allow_pytest=True)
237
241
 
238
242
 
239
243
  class TestLiftedEvent:
@@ -12,7 +12,7 @@ from hypothesis.strategies import booleans, integers
12
12
  from pytest import LogCaptureFixture, mark, param, raises
13
13
 
14
14
  from tests.conftest import SKIPIF_CI_AND_WINDOWS
15
- from utilities.hypothesis import pairs, temp_paths, text_ascii, zoned_date_times
15
+ from utilities.hypothesis import pairs, temp_paths, zoned_date_times
16
16
  from utilities.iterables import one
17
17
  from utilities.logging import (
18
18
  GetLoggingLevelNumberError,
@@ -78,7 +78,7 @@ class TestBasicConfig:
78
78
  ) -> None:
79
79
  name = unique_str()
80
80
  with set_log_factory:
81
- basic_config(obj=name, filters=filters, plain=plain)
81
+ basic_config(obj=name, filters=filters, plain=plain, allow_pytest=True)
82
82
  getLogger(name).warning("message")
83
83
  record = one(r for r in caplog.records if r.name == name)
84
84
  assert record.message == "message"
@@ -527,13 +527,19 @@ class TestToLogger:
527
527
  def test_default(self) -> None:
528
528
  assert to_logger().name == "root"
529
529
 
530
- @given(name=text_ascii(min_size=1))
531
- def test_logger(self, *, name: str) -> None:
530
+ def test_logger(self) -> None:
531
+ name = unique_str()
532
532
  assert to_logger(getLogger(name)).name == name
533
533
 
534
- @given(name=text_ascii(min_size=1))
535
- def test_str(self, *, name: str) -> None:
534
+ def test_str(self) -> None:
535
+ name = unique_str()
536
536
  assert to_logger(name).name == name
537
537
 
538
+ @mark.parametrize(("allow_pytest", "expected"), [param(False, 1), param(True, 0)])
539
+ def test_allow_pytest(self, *, allow_pytest: bool, expected: int) -> None:
540
+ name = unique_str()
541
+ logger = to_logger(name, allow_pytest=allow_pytest)
542
+ assert len(logger.filters) == expected
543
+
538
544
  def test_none(self) -> None:
539
545
  assert to_logger(None).name == "root"
@@ -22,6 +22,7 @@ from utilities.os import (
22
22
  get_cpu_use,
23
23
  get_env_var,
24
24
  is_debug,
25
+ is_pytest,
25
26
  temp_environ,
26
27
  )
27
28
  from utilities.pytest import skipif_windows
@@ -124,6 +125,11 @@ class TestIsDebug:
124
125
  assert isinstance(result, bool)
125
126
 
126
127
 
128
+ class TestIsPytest:
129
+ def test_main(self) -> None:
130
+ assert is_pytest()
131
+
132
+
127
133
  class TestTempEnviron:
128
134
  @given(key=text.map(_prefix), value=text)
129
135
  def test_set(self, *, key: str, value: str) -> None:
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from asyncio import TaskGroup
4
+ from typing import TYPE_CHECKING, ClassVar
5
+
6
+ from pottery import AIORedlock
7
+ from pytest import mark, param, raises
8
+
9
+ from utilities.asyncio import sleep_td
10
+ from utilities.pottery import (
11
+ _YieldAccessNumLocksError,
12
+ _YieldAccessUnableToAcquireLockError,
13
+ extend_lock,
14
+ yield_access,
15
+ )
16
+ from utilities.text import unique_str
17
+ from utilities.timer import Timer
18
+ from utilities.whenever import SECOND
19
+
20
+ if TYPE_CHECKING:
21
+ from redis.asyncio import Redis
22
+ from whenever import TimeDelta
23
+
24
+
25
+ class TestExtendLock:
26
+ async def test_main(self, *, test_redis: Redis) -> None:
27
+ lock = AIORedlock(key=unique_str(), masters={test_redis})
28
+ async with lock:
29
+ await extend_lock(lock=lock)
30
+
31
+ async def test_none(self) -> None:
32
+ await extend_lock()
33
+
34
+
35
+ class TestYieldAccess:
36
+ delta: ClassVar[TimeDelta] = 0.1 * SECOND
37
+
38
+ @mark.parametrize(
39
+ ("num_tasks", "num_locks", "min_multiple"),
40
+ [
41
+ param(1, 1, 1),
42
+ param(1, 2, 1),
43
+ param(1, 3, 1),
44
+ param(2, 1, 2),
45
+ param(2, 2, 1),
46
+ param(2, 3, 1),
47
+ param(2, 4, 1),
48
+ param(2, 5, 1),
49
+ param(3, 1, 3),
50
+ param(3, 2, 2),
51
+ param(3, 3, 1),
52
+ param(3, 4, 1),
53
+ param(3, 5, 1),
54
+ param(4, 1, 4),
55
+ param(4, 2, 2),
56
+ param(4, 3, 2),
57
+ param(4, 4, 1),
58
+ param(4, 5, 1),
59
+ ],
60
+ )
61
+ async def test_main(
62
+ self, *, test_redis: Redis, num_tasks: int, num_locks: int, min_multiple: int
63
+ ) -> None:
64
+ with Timer() as timer:
65
+ await self.func(test_redis, num_tasks, unique_str(), num_locks=num_locks)
66
+ assert (min_multiple * self.delta) <= timer <= (5 * min_multiple * self.delta)
67
+
68
+ async def test_error_num_locks(self, *, test_redis: Redis) -> None:
69
+ with raises(
70
+ _YieldAccessNumLocksError,
71
+ match=r"Number of locks for '\w+' must be positive; got 0",
72
+ ):
73
+ async with yield_access(test_redis, unique_str(), num=0):
74
+ ...
75
+
76
+ async def test_error_unable_to_acquire_lock(self, *, test_redis: Redis) -> None:
77
+ key = unique_str()
78
+ delta = 0.1 * SECOND
79
+
80
+ async def coroutine(key: str, /) -> None:
81
+ async with yield_access(
82
+ test_redis, key, num=1, timeout_acquire=delta, throttle=5 * delta
83
+ ):
84
+ await sleep_td(delta)
85
+
86
+ with raises(ExceptionGroup) as exc_info:
87
+ async with TaskGroup() as tg:
88
+ _ = tg.create_task(coroutine(key))
89
+ _ = tg.create_task(coroutine(key))
90
+ assert exc_info.group_contains(
91
+ _YieldAccessUnableToAcquireLockError,
92
+ match=r"Unable to acquire any 1 of 1 locks for '\w+' after .*",
93
+ )
94
+
95
+ async def func(
96
+ self, redis: Redis, num_tasks: int, key: str, /, *, num_locks: int = 1
97
+ ) -> None:
98
+ async def coroutine() -> None:
99
+ async with yield_access(redis, key, num=num_locks):
100
+ await sleep_td(self.delta)
101
+
102
+ async with TaskGroup() as tg:
103
+ _ = [tg.create_task(coroutine()) for _ in range(num_tasks)]
@@ -9,7 +9,7 @@ from pytest import fixture, mark, param, raises
9
9
 
10
10
  from utilities.iterables import one
11
11
  from utilities.os import temp_environ
12
- from utilities.pytest import NodeIdToPathError, is_pytest, node_id_path, throttle
12
+ from utilities.pytest import NodeIdToPathError, node_id_path, throttle
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from collections.abc import Sequence
@@ -25,11 +25,6 @@ def set_asyncio_default_fixture_loop_scope(*, testdir: Testdir) -> None:
25
25
  """)
26
26
 
27
27
 
28
- class TestIsPytest:
29
- def test_main(self) -> None:
30
- assert is_pytest()
31
-
32
-
33
28
  class TestNodeIdPath:
34
29
  @mark.parametrize(
35
30
  ("node_id", "expected"),
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.155.4"
3
+ __version__ = "0.157.0"
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- import sys
5
4
  from asyncio import (
6
5
  Lock,
7
6
  Queue,
@@ -36,9 +35,8 @@ from typing import (
36
35
  override,
37
36
  )
38
37
 
39
- from utilities.errors import ImpossibleCaseError, is_instance_error
40
38
  from utilities.functions import ensure_int, ensure_not_none
41
- from utilities.logging import to_logger
39
+ from utilities.os import is_pytest
42
40
  from utilities.random import SYSTEM_RANDOM
43
41
  from utilities.sentinel import Sentinel, sentinel
44
42
  from utilities.shelve import yield_shelf
@@ -70,8 +68,6 @@ if TYPE_CHECKING:
70
68
  from utilities.types import (
71
69
  Coro,
72
70
  Delta,
73
- ExceptionTypeLike,
74
- LoggerLike,
75
71
  MaybeCallableBoolLike,
76
72
  MaybeType,
77
73
  PathLike,
@@ -369,8 +365,6 @@ async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> li
369
365
  try:
370
366
  items = [await queue.get()]
371
367
  except RuntimeError as error: # pragma: no cover
372
- from utilities.pytest import is_pytest
373
-
374
368
  if (not is_pytest()) or (error.args[0] != "Event loop is closed"):
375
369
  raise
376
370
  return []
@@ -400,43 +394,6 @@ def get_items_nowait[T](queue: Queue[T], /, *, max_size: int | None = None) -> l
400
394
  ##
401
395
 
402
396
 
403
- async def loop_until_succeed(
404
- func: Callable[[], Coro[None]],
405
- /,
406
- *,
407
- logger: LoggerLike | None = None,
408
- errors: ExceptionTypeLike[Exception] | None = None,
409
- sleep: Delta | None = None,
410
- ) -> bool:
411
- """Repeatedly call a coroutine until it succeeds."""
412
- name = get_coroutine_name(func)
413
- while True:
414
- try:
415
- await func()
416
- except Exception as error: # noqa: BLE001
417
- if logger is not None:
418
- to_logger(logger).error("Error running %r", name, exc_info=True)
419
- exc_type, exc_value, traceback = sys.exc_info()
420
- if (exc_type is None) or (exc_value is None): # pragma: no cover
421
- raise ImpossibleCaseError(
422
- case=[f"{exc_type=}", f"{exc_value=}"]
423
- ) from None
424
- sys.excepthook(exc_type, exc_value, traceback)
425
- if (errors is not None) and is_instance_error(error, errors):
426
- return False
427
- if sleep is not None:
428
- if logger is not None:
429
- to_logger(logger).info("Sleeping for %s...", sleep)
430
- await sleep_td(sleep)
431
- if logger is not None:
432
- to_logger(logger).info("Retrying %r...", name)
433
- else:
434
- return True
435
-
436
-
437
- ##
438
-
439
-
440
397
  async def put_items[T](items: Iterable[T], queue: Queue[T], /) -> None:
441
398
  """Put items into a queue; if full then wait."""
442
399
  for item in items:
@@ -589,7 +546,6 @@ __all__ = [
589
546
  "get_coroutine_name",
590
547
  "get_items",
591
548
  "get_items_nowait",
592
- "loop_until_succeed",
593
549
  "put_items",
594
550
  "put_items_nowait",
595
551
  "sleep_max",
@@ -6,7 +6,6 @@ import pathlib
6
6
  import uuid
7
7
  from typing import TYPE_CHECKING, TypedDict, assert_never, override
8
8
 
9
- import click
10
9
  import whenever
11
10
  from click import Choice, Context, Parameter, ParamType
12
11
  from click.types import IntParamType, StringParamType
@@ -29,6 +28,7 @@ if TYPE_CHECKING:
29
28
  IPv6AddressLike,
30
29
  MaybeStr,
31
30
  MonthDayLike,
31
+ PathLike,
32
32
  PlainDateTimeLike,
33
33
  TimeDeltaLike,
34
34
  TimeLike,
@@ -37,16 +37,6 @@ if TYPE_CHECKING:
37
37
  )
38
38
 
39
39
 
40
- FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
41
- DirPath = click.Path(file_okay=False, dir_okay=True, path_type=pathlib.Path)
42
- ExistingFilePath = click.Path(
43
- exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path
44
- )
45
- ExistingDirPath = click.Path(
46
- exists=True, file_okay=False, dir_okay=True, path_type=pathlib.Path
47
- )
48
-
49
-
50
40
  class _HelpOptionNames(TypedDict):
51
41
  help_option_names: Sequence[str]
52
42
 
@@ -252,6 +242,32 @@ class MonthDay(ParamType):
252
242
  assert_never(never)
253
243
 
254
244
 
245
+ class Path(ParamType):
246
+ """A path-valued parameter."""
247
+
248
+ name = "path"
249
+
250
+ @override
251
+ def __repr__(self) -> str:
252
+ return self.name.upper()
253
+
254
+ @override
255
+ def convert(
256
+ self, value: PathLike, param: Parameter | None, ctx: Context | None
257
+ ) -> pathlib.Path:
258
+ """Convert a value into the `Path` type."""
259
+ match value:
260
+ case pathlib.Path():
261
+ return value.expanduser()
262
+ case str():
263
+ try:
264
+ return pathlib.Path(value).expanduser()
265
+ except ValueError as error:
266
+ self.fail(str(error), param, ctx)
267
+ case never:
268
+ assert_never(never)
269
+
270
+
255
271
  class PlainDateTime(ParamType):
256
272
  """A local-datetime-valued parameter."""
257
273
 
@@ -592,11 +608,7 @@ __all__ = [
592
608
  "Date",
593
609
  "DateDelta",
594
610
  "DateTimeDelta",
595
- "DirPath",
596
611
  "Enum",
597
- "ExistingDirPath",
598
- "ExistingFilePath",
599
- "FilePath",
600
612
  "FrozenSetChoices",
601
613
  "FrozenSetEnums",
602
614
  "FrozenSetParameter",
@@ -609,6 +621,8 @@ __all__ = [
609
621
  "ListParameter",
610
622
  "ListStrs",
611
623
  "MonthDay",
624
+ "Path",
625
+ "Path",
612
626
  "PlainDateTime",
613
627
  "Time",
614
628
  "TimeDelta",