dycw-utilities 0.129.1__tar.gz → 0.129.3__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 (228) hide show
  1. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/PKG-INFO +1 -1
  2. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/pyproject.toml +2 -2
  3. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_asyncio.py +93 -38
  4. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_sqlalchemy.py +1 -0
  5. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/asyncio.py +53 -51
  7. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/.gitignore +0 -0
  8. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/LICENSE +0 -0
  9. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/README.md +0 -0
  10. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/__init__.py +0 -0
  11. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/conftest.py +0 -0
  12. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/__init__.py +0 -0
  13. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_missing/__init__.py +0 -0
  14. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_missing/module.py +0 -0
  15. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/__init__.py +0 -0
  16. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/outer_1.py +0 -0
  17. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/outer_2.py +0 -0
  18. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  19. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  20. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  21. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  22. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_without/__init__.py +0 -0
  23. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_without/module_1.py +0 -0
  24. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/package_without/module_2.py +0 -0
  25. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/standalone.py +0 -0
  26. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/modules/with_imports.py +0 -0
  27. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  28. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  29. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  30. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  31. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  32. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  33. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  34. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  35. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_altair.py +0 -0
  36. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_asyncio_classes/__init__.py +0 -0
  37. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_asyncio_classes/loopers.py +0 -0
  38. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_asyncio_classes/redis.py +0 -0
  39. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_atomicwrites.py +0 -0
  40. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_atools.py +0 -0
  41. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_cachetools.py +0 -0
  42. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_click.py +0 -0
  43. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_concurrent.py +0 -0
  44. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_contextlib.py +0 -0
  45. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_contextvars.py +0 -0
  46. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_cryptography.py +0 -0
  47. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_cvxpy.py +0 -0
  48. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_dataclasses.py +0 -0
  49. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_datetime.py +0 -0
  50. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_enum.py +0 -0
  51. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_errors.py +0 -0
  52. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_eventkit.py +0 -0
  53. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_fastapi.py +0 -0
  54. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_fpdf2.py +0 -0
  55. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_functions.py +0 -0
  56. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_functools.py +0 -0
  57. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_getpass.py +0 -0
  58. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_hashlib.py +0 -0
  59. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_http.py +0 -0
  60. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_hypothesis.py +0 -0
  61. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_importlib.py +0 -0
  62. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_ipython.py +0 -0
  63. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_iterables.py +0 -0
  64. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_jupyter.py +0 -0
  65. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_libcst.py +0 -0
  66. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_lightweight_charts.py +0 -0
  67. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_logging.py +0 -0
  68. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_loguru.py +0 -0
  69. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_luigi.py +0 -0
  70. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_math.py +0 -0
  71. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_memory_profiler.py +0 -0
  72. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_modules.py +0 -0
  73. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_more_itertools.py +0 -0
  74. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_numpy.py +0 -0
  75. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_operator.py +0 -0
  76. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_optuna.py +0 -0
  77. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_orjson.py +0 -0
  78. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_os.py +0 -0
  79. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_parse.py +0 -0
  80. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pathlib.py +0 -0
  81. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_period.py +0 -0
  82. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pickle.py +0 -0
  83. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_platform.py +0 -0
  84. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_polars.py +0 -0
  85. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_polars_ols.py +0 -0
  86. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pottery.py +0 -0
  87. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pqdm.py +0 -0
  88. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_psutil.py +0 -0
  89. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pydantic.py +0 -0
  90. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pyinstrument.py +0 -0
  91. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pyrsistent.py +0 -0
  92. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pytest.py +0 -0
  93. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_pytest_regressions.py +0 -0
  94. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_python_dotenv.py +0 -0
  95. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_random.py +0 -0
  96. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_re.py +0 -0
  97. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_redis.py +0 -0
  98. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_reprlib.py +0 -0
  99. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_scipy.py +0 -0
  100. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_sentinel.py +0 -0
  101. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_shelve.py +0 -0
  102. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_slack_sdk.py +0 -0
  103. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_socket.py +0 -0
  104. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_sqlalchemy_polars.py +0 -0
  105. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_statsmodel.py +0 -0
  106. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_streamlit.py +0 -0
  107. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_string.py +0 -0
  108. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_sys.py +0 -0
  109. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_tempfile.py +0 -0
  110. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_tenacity.py +0 -0
  111. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_text.py +0 -0
  112. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_threading.py +0 -0
  113. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_timer.py +0 -0
  114. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback.py +0 -0
  115. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/__init__.py +0 -0
  116. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/chain.py +0 -0
  117. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  118. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  119. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  120. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/many.py +0 -0
  121. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/one.py +0 -0
  122. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/recursive.py +0 -0
  123. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  124. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  125. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/two.py +0 -0
  126. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_traceback_funcs/untraced.py +0 -0
  127. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_types.py +0 -0
  128. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_typing.py +0 -0
  129. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_typing_funcs/__init__.py +0 -0
  130. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_typing_funcs/no_future.py +0 -0
  131. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_typing_funcs/with_future.py +0 -0
  132. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_tzdata.py +0 -0
  133. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_tzlocal.py +0 -0
  134. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_uuid.py +0 -0
  135. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_version.py +0 -0
  136. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_warnings.py +0 -0
  137. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_whenever.py +0 -0
  138. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_zipfile.py +0 -0
  139. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/tests/test_zoneinfo.py +0 -0
  140. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/altair.py +0 -0
  141. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/atomicwrites.py +0 -0
  142. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/atools.py +0 -0
  143. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/cachetools.py +0 -0
  144. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/click.py +0 -0
  145. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/concurrent.py +0 -0
  146. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/contextlib.py +0 -0
  147. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/contextvars.py +0 -0
  148. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/cryptography.py +0 -0
  149. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/cvxpy.py +0 -0
  150. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/dataclasses.py +0 -0
  151. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/datetime.py +0 -0
  152. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/enum.py +0 -0
  153. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/errors.py +0 -0
  154. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/eventkit.py +0 -0
  155. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/fastapi.py +0 -0
  156. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/fpdf2.py +0 -0
  157. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/functions.py +0 -0
  158. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/functools.py +0 -0
  159. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/getpass.py +0 -0
  160. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/hashlib.py +0 -0
  161. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/http.py +0 -0
  162. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/hypothesis.py +0 -0
  163. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/importlib.py +0 -0
  164. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/ipython.py +0 -0
  165. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/iterables.py +0 -0
  166. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/jupyter.py +0 -0
  167. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/libcst.py +0 -0
  168. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/lightweight_charts.py +0 -0
  169. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/logging.py +0 -0
  170. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/loguru.py +0 -0
  171. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/luigi.py +0 -0
  172. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/math.py +0 -0
  173. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/memory_profiler.py +0 -0
  174. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/modules.py +0 -0
  175. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/more_itertools.py +0 -0
  176. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/numpy.py +0 -0
  177. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/operator.py +0 -0
  178. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/optuna.py +0 -0
  179. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/orjson.py +0 -0
  180. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/os.py +0 -0
  181. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/parse.py +0 -0
  182. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pathlib.py +0 -0
  183. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/period.py +0 -0
  184. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pickle.py +0 -0
  185. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/platform.py +0 -0
  186. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/polars.py +0 -0
  187. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/polars_ols.py +0 -0
  188. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pottery.py +0 -0
  189. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pqdm.py +0 -0
  190. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/psutil.py +0 -0
  191. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/py.typed +0 -0
  192. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pydantic.py +0 -0
  193. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pyinstrument.py +0 -0
  194. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pyrsistent.py +0 -0
  195. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pytest.py +0 -0
  196. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/pytest_regressions.py +0 -0
  197. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/python_dotenv.py +0 -0
  198. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/random.py +0 -0
  199. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/re.py +0 -0
  200. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/redis.py +0 -0
  201. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/reprlib.py +0 -0
  202. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/scipy.py +0 -0
  203. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/sentinel.py +0 -0
  204. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/shelve.py +0 -0
  205. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/slack_sdk.py +0 -0
  206. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/socket.py +0 -0
  207. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/sqlalchemy.py +0 -0
  208. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/sqlalchemy_polars.py +0 -0
  209. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/statsmodels.py +0 -0
  210. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/streamlit.py +0 -0
  211. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/string.py +0 -0
  212. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/sys.py +0 -0
  213. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/tempfile.py +0 -0
  214. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/tenacity.py +0 -0
  215. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/text.py +0 -0
  216. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/threading.py +0 -0
  217. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/timer.py +0 -0
  218. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/traceback.py +0 -0
  219. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/types.py +0 -0
  220. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/typing.py +0 -0
  221. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/tzdata.py +0 -0
  222. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/tzlocal.py +0 -0
  223. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/uuid.py +0 -0
  224. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/version.py +0 -0
  225. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/warnings.py +0 -0
  226. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/whenever.py +0 -0
  227. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.129.1 → dycw_utilities-0.129.3}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.129.1
3
+ Version: 0.129.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -94,7 +94,7 @@ dependencies = [
94
94
  name = "dycw-utilities"
95
95
  readme = "README.md"
96
96
  requires-python = ">= 3.12"
97
- version = "0.129.1"
97
+ version = "0.129.3"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
@@ -334,7 +334,7 @@ zzz-test-zoneinfo = [
334
334
  # bump-my-version
335
335
  [tool.bumpversion]
336
336
  allow_dirty = true
337
- current_version = "0.129.1"
337
+ current_version = "0.129.3"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -21,7 +21,10 @@ from hypothesis.strategies import (
21
21
  )
22
22
  from pytest import LogCaptureFixture, approx, mark, param, raises
23
23
 
24
+ from tests.conftest import IS_CI
24
25
  from tests.test_asyncio_classes.loopers import (
26
+ _BACKOFF,
27
+ _FREQ,
25
28
  _REL,
26
29
  CountingLooper,
27
30
  CountingLooperError,
@@ -63,6 +66,7 @@ from utilities.sentinel import Sentinel, sentinel
63
66
  from utilities.timer import Timer
64
67
 
65
68
  if TYPE_CHECKING:
69
+ import datetime as dt
66
70
  from collections.abc import AsyncIterator
67
71
 
68
72
  from utilities.types import Duration, MaybeCallableEvent
@@ -255,9 +259,15 @@ class TestGetItems:
255
259
 
256
260
 
257
261
  class TestLooper:
258
- sleep_if_failure_cases: ClassVar[list[Any]] = [
259
- param(True, "; sleeping for .*"),
260
- param(False, ""),
262
+ _restart_min_elapsed: ClassVar[dt.timedelta] = datetime_duration_to_timedelta(
263
+ (0.8 if IS_CI else 1.0) * _BACKOFF
264
+ )
265
+ _restart_max_elapsed: ClassVar[dt.timedelta] = datetime_duration_to_timedelta(
266
+ (1.2 if IS_CI else 1.0) * _FREQ
267
+ )
268
+ skip_sleep_if_failure_cases: ClassVar[list[Any]] = [
269
+ param(True, ""),
270
+ param(False, "; sleeping for .*"),
261
271
  ]
262
272
 
263
273
  async def test_main_with_nothing(self) -> None:
@@ -330,16 +340,16 @@ class TestLooper:
330
340
  @override
331
341
  async def _initialize_core(self) -> None:
332
342
  if self._initialization_attempts == 1:
333
- _ = await super().initialize(sleep_if_failure=False)
343
+ _ = await super().initialize()
334
344
  await super()._initialize_core()
335
345
 
336
346
  looper = Example()
337
- _ = await looper.initialize(sleep_if_failure=False)
347
+ _ = await looper.initialize()
338
348
  _ = one(m for m in caplog.messages if search(": already initializing$", m))
339
349
 
340
- @mark.parametrize(("sleep_if_failure", "extra"), sleep_if_failure_cases)
350
+ @mark.parametrize(("skip_sleep_if_failure", "extra"), skip_sleep_if_failure_cases)
341
351
  async def test_initialize_failure(
342
- self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
352
+ self, *, skip_sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
343
353
  ) -> None:
344
354
  class Example(CountingLooper):
345
355
  @override
@@ -349,7 +359,7 @@ class TestLooper:
349
359
  await super()._initialize_core()
350
360
 
351
361
  looper = Example()
352
- _ = await looper.initialize(sleep_if_failure=sleep_if_failure)
362
+ _ = await looper.initialize(skip_sleep_if_failure=skip_sleep_if_failure)
353
363
  pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) whilst initializing{extra}$"
354
364
  _ = one(m for m in caplog.messages if search(pattern, m))
355
365
 
@@ -443,6 +453,40 @@ class TestLooper:
443
453
  looper = CountingLooper().replace(freq=10.0)
444
454
  assert looper.freq == 10.0
445
455
 
456
+ async def test_request_back_off(self) -> None:
457
+ class Example(CountingLooper):
458
+ @override
459
+ async def core(self) -> None:
460
+ await super().core()
461
+ if (self._initialization_attempts >= 2) and (
462
+ self.count >= (self.max_count / 2)
463
+ ):
464
+ self.request_back_off()
465
+
466
+ looper = Example(auto_start=True, timeout=1.0)
467
+ async with looper:
468
+ ...
469
+ assert_looper_stats(
470
+ looper,
471
+ entries=1,
472
+ core_successes=23,
473
+ core_failures=2,
474
+ initialization_successes=3,
475
+ tear_down_successes=2,
476
+ restart_successes=2,
477
+ stops=1,
478
+ )
479
+
480
+ async def test_request_back_off_already_requested(
481
+ self, *, caplog: LogCaptureFixture
482
+ ) -> None:
483
+ looper = CountingLooper()
484
+ for _ in range(2):
485
+ looper.request_back_off()
486
+ _ = one(
487
+ m for m in caplog.messages if search(r": already requested back off$", m)
488
+ )
489
+
446
490
  async def test_request_restart(self) -> None:
447
491
  class Example(CountingLooper):
448
492
  @override
@@ -459,11 +503,11 @@ class TestLooper:
459
503
  assert_looper_stats(
460
504
  looper,
461
505
  entries=1,
462
- core_successes=79,
506
+ core_successes=35,
463
507
  core_failures=1,
464
- initialization_successes=16,
465
- tear_down_successes=15,
466
- restart_successes=15,
508
+ initialization_successes=7,
509
+ tear_down_successes=6,
510
+ restart_successes=6,
467
511
  stops=1,
468
512
  )
469
513
 
@@ -553,9 +597,8 @@ class TestLooper:
553
597
  if search(r": already requested stop when empty$", m)
554
598
  )
555
599
 
556
- @mark.parametrize(("sleep_if_failure", "extra"), sleep_if_failure_cases)
557
600
  async def test_restart_failure_during_initialization(
558
- self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
601
+ self, *, caplog: LogCaptureFixture
559
602
  ) -> None:
560
603
  class Example(CountingLooper):
561
604
  @override
@@ -565,13 +608,14 @@ class TestLooper:
565
608
  await super()._initialize_core()
566
609
 
567
610
  looper = Example()
568
- await looper.restart(sleep_if_failure=sleep_if_failure)
569
- pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) whilst restarting \(initialize\){extra}$"
611
+ with Timer() as timer:
612
+ await looper.restart()
613
+ assert timer.timedelta >= self._restart_min_elapsed
614
+ pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) whilst restarting \(initialize\); sleeping for .*\.\.\.$"
570
615
  _ = one(m for m in caplog.messages if search(pattern, m))
571
616
 
572
- @mark.parametrize(("sleep_if_failure", "extra"), sleep_if_failure_cases)
573
617
  async def test_restart_failure_during_tear_down(
574
- self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
618
+ self, *, caplog: LogCaptureFixture
575
619
  ) -> None:
576
620
  class Example(CountingLooper):
577
621
  @override
@@ -581,13 +625,14 @@ class TestLooper:
581
625
  await super()._tear_down_core()
582
626
 
583
627
  looper = Example()
584
- await looper.restart(sleep_if_failure=sleep_if_failure)
585
- pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) whilst restarting \(tear down\){extra}$"
628
+ with Timer() as timer:
629
+ await looper.restart()
630
+ assert timer.timedelta >= self._restart_min_elapsed
631
+ pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) whilst restarting \(tear down\); sleeping for .*\.\.\.$"
586
632
  _ = one(m for m in caplog.messages if search(pattern, m))
587
633
 
588
- @mark.parametrize(("sleep_if_failure", "extra"), sleep_if_failure_cases)
589
634
  async def test_restart_failure_during_tear_down_and_initialization(
590
- self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
635
+ self, *, caplog: LogCaptureFixture
591
636
  ) -> None:
592
637
  class Example(CountingLooper):
593
638
  @override
@@ -603,8 +648,18 @@ class TestLooper:
603
648
  await super()._tear_down_core()
604
649
 
605
650
  looper = Example()
606
- await looper.restart(sleep_if_failure=sleep_if_failure)
607
- pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) \(tear down\) and then {get_class_name(CountingLooperError)}\(\) \(initialization\) whilst restarting{extra}$"
651
+ with Timer() as timer:
652
+ await looper.restart()
653
+ assert timer.timedelta >= self._restart_min_elapsed
654
+ pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) \(tear down\) and then {get_class_name(CountingLooperError)}\(\) \(initialization\) whilst restarting; sleeping for .*\.\.\.$"
655
+ _ = one(m for m in caplog.messages if search(pattern, m))
656
+
657
+ async def test_restart_success(self, *, caplog: LogCaptureFixture) -> None:
658
+ looper = CountingLooper()
659
+ with Timer() as timer:
660
+ await looper.restart()
661
+ assert timer.timedelta <= self._restart_max_elapsed
662
+ pattern = r": finished restarting$"
608
663
  _ = one(m for m in caplog.messages if search(pattern, m))
609
664
 
610
665
  @mark.parametrize("n", [param(0), param(1), param(2)])
@@ -698,16 +753,16 @@ class TestLooper:
698
753
  @override
699
754
  async def _tear_down_core(self) -> None:
700
755
  if self._tear_down_attempts == 1:
701
- _ = await super().tear_down(sleep_if_failure=False)
756
+ _ = await super().tear_down()
702
757
  await super()._tear_down_core()
703
758
 
704
759
  looper = Example()
705
- _ = await looper.tear_down(sleep_if_failure=False)
760
+ _ = await looper.tear_down()
706
761
  _ = one(m for m in caplog.messages if search(": already tearing down$", m))
707
762
 
708
- @mark.parametrize(("sleep_if_failure", "extra"), sleep_if_failure_cases)
763
+ @mark.parametrize(("skip_sleep_if_failure", "extra"), skip_sleep_if_failure_cases)
709
764
  async def test_tear_down_failure(
710
- self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
765
+ self, *, skip_sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
711
766
  ) -> None:
712
767
  class Example(CountingLooper):
713
768
  @override
@@ -717,7 +772,7 @@ class TestLooper:
717
772
  await super()._tear_down_core()
718
773
 
719
774
  looper = Example()
720
- _ = await looper.tear_down(sleep_if_failure=sleep_if_failure)
775
+ _ = await looper.tear_down(skip_sleep_if_failure=skip_sleep_if_failure)
721
776
  pattern = rf": encountered {get_class_name(CountingLooperError)}\(\) whilst tearing down{extra}$"
722
777
  _ = one(m for m in caplog.messages if search(pattern, m))
723
778
 
@@ -751,7 +806,7 @@ class TestLooper:
751
806
  assert_looper_stats(
752
807
  looper,
753
808
  entries=1,
754
- core_successes=45,
809
+ core_successes=47,
755
810
  core_failures=5,
756
811
  initialization_successes=6,
757
812
  tear_down_successes=5,
@@ -767,10 +822,10 @@ class TestLooper:
767
822
  looper,
768
823
  entries=1,
769
824
  core_successes=56,
770
- core_failures=13,
771
- initialization_successes=14,
772
- tear_down_successes=13,
773
- restart_successes=13,
825
+ core_failures=14,
826
+ initialization_successes=15,
827
+ tear_down_successes=14,
828
+ restart_successes=14,
774
829
  stops=stops,
775
830
  rel=rel,
776
831
  )
@@ -781,7 +836,7 @@ class TestLooper:
781
836
  assert_looper_stats(
782
837
  looper,
783
838
  entries=1,
784
- core_successes=49,
839
+ core_successes=48,
785
840
  core_failures=24,
786
841
  initialization_successes=25,
787
842
  tear_down_successes=24,
@@ -798,9 +853,9 @@ class TestLooper:
798
853
  entries=1,
799
854
  core_successes=35,
800
855
  core_failures=35,
801
- initialization_successes=35,
802
- tear_down_successes=34,
803
- restart_successes=34,
856
+ initialization_successes=36,
857
+ tear_down_successes=35,
858
+ restart_successes=35,
804
859
  stops=stops,
805
860
  rel=rel,
806
861
  )
@@ -1332,6 +1332,7 @@ class TestUpsertItems:
1332
1332
  await upsert_items(engine, item)
1333
1333
 
1334
1334
  @given(data=data(), triples=_upsert_lists(nullable=True, min_size=1))
1335
+ @mark.flaky
1335
1336
  @settings_with_reduced_examples(phases={Phase.generate})
1336
1337
  async def test_multiple_elements_with_the_same_primary_key(
1337
1338
  self, *, data: DataObject, triples: list[tuple[int, bool, bool | None]]
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.129.1"
3
+ __version__ = "0.129.3"
@@ -353,6 +353,7 @@ class Looper(Generic[_T]):
353
353
  _is_entered: Event = field(default_factory=Event, init=False, repr=False)
354
354
  _is_initialized: Event = field(default_factory=Event, init=False, repr=False)
355
355
  _is_initializing: Event = field(default_factory=Event, init=False, repr=False)
356
+ _is_pending_back_off: Event = field(default_factory=Event, init=False, repr=False)
356
357
  _is_pending_restart: Event = field(default_factory=Event, init=False, repr=False)
357
358
  _is_pending_stop: Event = field(default_factory=Event, init=False, repr=False)
358
359
  _is_pending_stop_when_empty: Event = field(
@@ -439,6 +440,11 @@ class Looper(Generic[_T]):
439
440
  def __len__(self) -> int:
440
441
  return self._queue.qsize()
441
442
 
443
+ async def _apply_back_off(self) -> None:
444
+ """Apply a back off period."""
445
+ await sleep(self._backoff)
446
+ self._is_pending_back_off.clear()
447
+
442
448
  async def core(self) -> None:
443
449
  """Core part of running the looper."""
444
450
 
@@ -458,7 +464,9 @@ class Looper(Generic[_T]):
458
464
  """Remove and return an item from the end of the queue without blocking."""
459
465
  return self._queue.get_right_nowait()
460
466
 
461
- async def initialize(self, *, sleep_if_failure: bool) -> Exception | None:
467
+ async def initialize(
468
+ self, *, skip_sleep_if_failure: bool = False
469
+ ) -> Exception | None:
462
470
  """Initialize the looper."""
463
471
  match self._is_initializing.is_set():
464
472
  case True:
@@ -476,21 +484,21 @@ class Looper(Generic[_T]):
476
484
  async with self._lock:
477
485
  self._initialization_failures += 1
478
486
  ret = error
479
- match sleep_if_failure:
487
+ match skip_sleep_if_failure:
480
488
  case True:
481
489
  _ = self._logger.warning(
482
- "%s: encountered %s whilst initializing; sleeping for %s...",
490
+ "%s: encountered %s whilst initializing",
483
491
  self,
484
492
  repr_error(error),
485
- self.backoff,
486
493
  )
487
- await sleep(self._backoff)
488
494
  case False:
489
495
  _ = self._logger.warning(
490
- "%s: encountered %s whilst initializing",
496
+ "%s: encountered %s whilst initializing; sleeping for %s...",
491
497
  self,
492
498
  repr_error(error),
499
+ self.backoff,
493
500
  )
501
+ await self._apply_back_off()
494
502
  case _ as never:
495
503
  assert_never(never)
496
504
  else:
@@ -547,6 +555,21 @@ class Looper(Generic[_T]):
547
555
  **kwargs,
548
556
  )
549
557
 
558
+ def request_back_off(self) -> None:
559
+ """Request the looper to back off."""
560
+ match self._is_pending_back_off.is_set():
561
+ case True:
562
+ _ = self._debug and self._logger.debug(
563
+ "%s: already requested back off", self
564
+ )
565
+ case False:
566
+ _ = self._debug and self._logger.debug(
567
+ "%s: requesting back off...", self
568
+ )
569
+ self._is_pending_back_off.set()
570
+ case _ as never:
571
+ assert_never(never)
572
+
550
573
  def request_restart(self) -> None:
551
574
  """Request the looper to restart."""
552
575
  match self._is_pending_restart.is_set():
@@ -561,6 +584,7 @@ class Looper(Generic[_T]):
561
584
  self._is_pending_restart.set()
562
585
  case _ as never:
563
586
  assert_never(never)
587
+ self.request_back_off()
564
588
 
565
589
  def request_stop(self) -> None:
566
590
  """Request the looper to stop."""
@@ -590,20 +614,20 @@ class Looper(Generic[_T]):
590
614
  case _ as never:
591
615
  assert_never(never)
592
616
 
593
- async def restart(self, *, sleep_if_failure: bool) -> None:
617
+ async def restart(self) -> None:
594
618
  """Restart the looper."""
595
619
  _ = self._debug and self._logger.debug("%s: restarting...", self)
596
620
  self._is_pending_restart.clear()
597
621
  async with self._lock:
598
622
  self._restart_attempts += 1
599
- tear_down = await self.tear_down(sleep_if_failure=False)
600
- initialization = await self.initialize(sleep_if_failure=False)
601
- match tear_down, initialization, sleep_if_failure:
602
- case None, None, bool():
623
+ tear_down = await self.tear_down(skip_sleep_if_failure=True)
624
+ initialization = await self.initialize(skip_sleep_if_failure=True)
625
+ match tear_down, initialization:
626
+ case None, None:
603
627
  _ = self._debug and self._logger.debug("%s: finished restarting", self)
604
628
  async with self._lock:
605
629
  self._restart_successes += 1
606
- case Exception(), None, True:
630
+ case Exception(), None:
607
631
  async with self._lock:
608
632
  self._restart_failures += 1
609
633
  _ = self._logger.warning(
@@ -612,16 +636,8 @@ class Looper(Generic[_T]):
612
636
  repr_error(tear_down),
613
637
  self.backoff,
614
638
  )
615
- await sleep(self._backoff)
616
- case Exception(), None, False:
617
- async with self._lock:
618
- self._restart_failures += 1
619
- _ = self._logger.warning(
620
- "%s: encountered %s whilst restarting (tear down)",
621
- self,
622
- repr_error(tear_down),
623
- )
624
- case None, Exception(), True:
639
+ await self._apply_back_off()
640
+ case None, Exception():
625
641
  async with self._lock:
626
642
  self._restart_failures += 1
627
643
  _ = self._logger.warning(
@@ -630,16 +646,8 @@ class Looper(Generic[_T]):
630
646
  repr_error(initialization),
631
647
  self.backoff,
632
648
  )
633
- await sleep(self._backoff)
634
- case None, Exception(), False:
635
- async with self._lock:
636
- self._restart_failures += 1
637
- _ = self._logger.warning(
638
- "%s: encountered %s whilst restarting (initialize)",
639
- self,
640
- repr_error(initialization),
641
- )
642
- case Exception(), Exception(), True:
649
+ await self._apply_back_off()
650
+ case Exception(), Exception():
643
651
  async with self._lock:
644
652
  self._restart_failures += 1
645
653
  _ = self._logger.warning(
@@ -649,16 +657,7 @@ class Looper(Generic[_T]):
649
657
  repr_error(initialization),
650
658
  self.backoff,
651
659
  )
652
- await sleep(self._backoff)
653
- case Exception(), Exception(), False:
654
- async with self._lock:
655
- self._restart_failures += 1
656
- _ = self._logger.warning(
657
- "%s: encountered %s (tear down) and then %s (initialization) whilst restarting",
658
- self,
659
- repr_error(tear_down),
660
- repr_error(initialization),
661
- )
660
+ await self._apply_back_off()
662
661
  case _ as never:
663
662
  assert_never(never)
664
663
 
@@ -674,10 +673,12 @@ class Looper(Generic[_T]):
674
673
  self._is_pending_stop_when_empty.is_set() and self.empty()
675
674
  ):
676
675
  await self.stop()
676
+ elif self._is_pending_back_off.is_set():
677
+ await self._apply_back_off()
677
678
  elif self._is_pending_restart.is_set():
678
- await self.restart(sleep_if_failure=True)
679
+ await self.restart()
679
680
  elif not self._is_initialized.is_set():
680
- _ = await self.initialize(sleep_if_failure=True)
681
+ _ = await self.initialize()
681
682
  else:
682
683
  _ = self._debug and self._logger.debug(
683
684
  "%s: running core...", self
@@ -695,7 +696,6 @@ class Looper(Generic[_T]):
695
696
  async with self._lock:
696
697
  self._core_failures += 1
697
698
  self.request_restart()
698
- await sleep(self._backoff)
699
699
  else:
700
700
  async with self._lock:
701
701
  self._core_successes += 1
@@ -749,7 +749,9 @@ class Looper(Generic[_T]):
749
749
  case _ as never:
750
750
  assert_never(never)
751
751
 
752
- async def tear_down(self, *, sleep_if_failure: bool) -> Exception | None:
752
+ async def tear_down(
753
+ self, *, skip_sleep_if_failure: bool = False
754
+ ) -> Exception | None:
753
755
  """Tear down the looper."""
754
756
  match self._is_tearing_down.is_set():
755
757
  case True:
@@ -766,21 +768,21 @@ class Looper(Generic[_T]):
766
768
  async with self._lock:
767
769
  self._tear_down_failures += 1
768
770
  ret = error
769
- match sleep_if_failure:
771
+ match skip_sleep_if_failure:
770
772
  case True:
771
773
  _ = self._logger.warning(
772
- "%s: encountered %s whilst tearing down; sleeping for %s...",
774
+ "%s: encountered %s whilst tearing down",
773
775
  self,
774
776
  repr_error(error),
775
- self.backoff,
776
777
  )
777
- await sleep(self._backoff)
778
778
  case False:
779
779
  _ = self._logger.warning(
780
- "%s: encountered %s whilst tearing down",
780
+ "%s: encountered %s whilst tearing down; sleeping for %s...",
781
781
  self,
782
782
  repr_error(error),
783
+ self.backoff,
783
784
  )
785
+ await self._apply_back_off()
784
786
  case _ as never:
785
787
  assert_never(never)
786
788
  else: