dycw-utilities 0.125.12__tar.gz → 0.125.14__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 (223) hide show
  1. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/PKG-INFO +1 -1
  2. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/pyproject.toml +2 -2
  3. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_asyncio.py +54 -20
  4. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/__init__.py +1 -1
  5. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/asyncio.py +120 -45
  6. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/.gitignore +0 -0
  7. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/LICENSE +0 -0
  8. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/README.md +0 -0
  9. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/__init__.py +0 -0
  10. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/conftest.py +0 -0
  11. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/__init__.py +0 -0
  12. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_missing/__init__.py +0 -0
  13. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_missing/module.py +0 -0
  14. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/__init__.py +0 -0
  15. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/outer_1.py +0 -0
  16. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/outer_2.py +0 -0
  17. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  18. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  19. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  20. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  21. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_without/__init__.py +0 -0
  22. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_without/module_1.py +0 -0
  23. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/package_without/module_2.py +0 -0
  24. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/standalone.py +0 -0
  25. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/modules/with_imports.py +0 -0
  26. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  27. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  28. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  29. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  30. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  31. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  32. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  33. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  34. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_altair.py +0 -0
  35. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_atomicwrites.py +0 -0
  36. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_atools.py +0 -0
  37. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_cachetools.py +0 -0
  38. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_click.py +0 -0
  39. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_concurrent.py +0 -0
  40. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_contextlib.py +0 -0
  41. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_contextvars.py +0 -0
  42. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_cryptography.py +0 -0
  43. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_cvxpy.py +0 -0
  44. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_dataclasses.py +0 -0
  45. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_datetime.py +0 -0
  46. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_enum.py +0 -0
  47. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_errors.py +0 -0
  48. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_eventkit.py +0 -0
  49. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_fastapi.py +0 -0
  50. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_fpdf2.py +0 -0
  51. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_functions.py +0 -0
  52. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_functools.py +0 -0
  53. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_getpass.py +0 -0
  54. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_git.py +0 -0
  55. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_hashlib.py +0 -0
  56. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_http.py +0 -0
  57. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_hypothesis.py +0 -0
  58. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_importlib.py +0 -0
  59. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_ipython.py +0 -0
  60. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_iterables.py +0 -0
  61. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_jupyter.py +0 -0
  62. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_libcst.py +0 -0
  63. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_lightweight_charts.py +0 -0
  64. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_logging.py +0 -0
  65. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_loguru.py +0 -0
  66. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_luigi.py +0 -0
  67. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_math.py +0 -0
  68. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_memory_profiler.py +0 -0
  69. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_modules.py +0 -0
  70. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_more_itertools.py +0 -0
  71. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_numpy.py +0 -0
  72. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_operator.py +0 -0
  73. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_optuna.py +0 -0
  74. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_orjson.py +0 -0
  75. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_os.py +0 -0
  76. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_parse.py +0 -0
  77. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pathlib.py +0 -0
  78. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_period.py +0 -0
  79. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pickle.py +0 -0
  80. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_platform.py +0 -0
  81. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_polars.py +0 -0
  82. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_polars_ols.py +0 -0
  83. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pqdm.py +0 -0
  84. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pydantic.py +0 -0
  85. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pyinstrument.py +0 -0
  86. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pyrsistent.py +0 -0
  87. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pytest.py +0 -0
  88. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_pytest_regressions.py +0 -0
  89. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_python_dotenv.py +0 -0
  90. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_random.py +0 -0
  91. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_re.py +0 -0
  92. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_redis.py +0 -0
  93. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_reprlib.py +0 -0
  94. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_rich.py +0 -0
  95. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_scipy.py +0 -0
  96. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_sentinel.py +0 -0
  97. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_shelve.py +0 -0
  98. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_slack_sdk.py +0 -0
  99. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_socket.py +0 -0
  100. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_sqlalchemy.py +0 -0
  101. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_sqlalchemy_polars.py +0 -0
  102. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_statsmodel.py +0 -0
  103. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_streamlit.py +0 -0
  104. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_sys.py +0 -0
  105. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_tempfile.py +0 -0
  106. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_tenacity.py +0 -0
  107. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_text.py +0 -0
  108. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_threading.py +0 -0
  109. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_timer.py +0 -0
  110. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback.py +0 -0
  111. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/__init__.py +0 -0
  112. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/chain.py +0 -0
  113. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  114. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  115. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  116. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/many.py +0 -0
  117. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/one.py +0 -0
  118. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/recursive.py +0 -0
  119. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  120. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  121. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/two.py +0 -0
  122. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_traceback_funcs/untraced.py +0 -0
  123. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_types.py +0 -0
  124. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_typing.py +0 -0
  125. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_typing_funcs/__init__.py +0 -0
  126. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_typing_funcs/no_future.py +0 -0
  127. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_typing_funcs/with_future.py +0 -0
  128. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_tzdata.py +0 -0
  129. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_tzlocal.py +0 -0
  130. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_uuid.py +0 -0
  131. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_version.py +0 -0
  132. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_warnings.py +0 -0
  133. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_whenever.py +0 -0
  134. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_zipfile.py +0 -0
  135. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/tests/test_zoneinfo.py +0 -0
  136. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/altair.py +0 -0
  137. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/atomicwrites.py +0 -0
  138. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/atools.py +0 -0
  139. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/cachetools.py +0 -0
  140. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/click.py +0 -0
  141. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/concurrent.py +0 -0
  142. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/contextlib.py +0 -0
  143. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/contextvars.py +0 -0
  144. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/cryptography.py +0 -0
  145. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/cvxpy.py +0 -0
  146. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/dataclasses.py +0 -0
  147. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/datetime.py +0 -0
  148. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/enum.py +0 -0
  149. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/errors.py +0 -0
  150. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/eventkit.py +0 -0
  151. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/fastapi.py +0 -0
  152. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/fpdf2.py +0 -0
  153. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/functions.py +0 -0
  154. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/functools.py +0 -0
  155. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/getpass.py +0 -0
  156. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/git.py +0 -0
  157. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/hashlib.py +0 -0
  158. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/http.py +0 -0
  159. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/hypothesis.py +0 -0
  160. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/importlib.py +0 -0
  161. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/ipython.py +0 -0
  162. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/iterables.py +0 -0
  163. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/jupyter.py +0 -0
  164. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/libcst.py +0 -0
  165. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/lightweight_charts.py +0 -0
  166. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/logging.py +0 -0
  167. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/loguru.py +0 -0
  168. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/luigi.py +0 -0
  169. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/math.py +0 -0
  170. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/memory_profiler.py +0 -0
  171. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/modules.py +0 -0
  172. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/more_itertools.py +0 -0
  173. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/numpy.py +0 -0
  174. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/operator.py +0 -0
  175. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/optuna.py +0 -0
  176. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/orjson.py +0 -0
  177. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/os.py +0 -0
  178. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/parse.py +0 -0
  179. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pathlib.py +0 -0
  180. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/period.py +0 -0
  181. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pickle.py +0 -0
  182. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/platform.py +0 -0
  183. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/polars.py +0 -0
  184. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/polars_ols.py +0 -0
  185. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pqdm.py +0 -0
  186. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/py.typed +0 -0
  187. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pydantic.py +0 -0
  188. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pyinstrument.py +0 -0
  189. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pyrsistent.py +0 -0
  190. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pytest.py +0 -0
  191. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/pytest_regressions.py +0 -0
  192. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/python_dotenv.py +0 -0
  193. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/random.py +0 -0
  194. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/re.py +0 -0
  195. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/redis.py +0 -0
  196. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/reprlib.py +0 -0
  197. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/rich.py +0 -0
  198. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/scipy.py +0 -0
  199. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/sentinel.py +0 -0
  200. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/shelve.py +0 -0
  201. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/slack_sdk.py +0 -0
  202. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/socket.py +0 -0
  203. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/sqlalchemy.py +0 -0
  204. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/sqlalchemy_polars.py +0 -0
  205. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/statsmodels.py +0 -0
  206. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/streamlit.py +0 -0
  207. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/sys.py +0 -0
  208. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/tempfile.py +0 -0
  209. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/tenacity.py +0 -0
  210. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/text.py +0 -0
  211. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/threading.py +0 -0
  212. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/timer.py +0 -0
  213. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/traceback.py +0 -0
  214. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/types.py +0 -0
  215. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/typing.py +0 -0
  216. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/tzdata.py +0 -0
  217. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/tzlocal.py +0 -0
  218. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/uuid.py +0 -0
  219. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/version.py +0 -0
  220. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/warnings.py +0 -0
  221. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/whenever.py +0 -0
  222. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/zipfile.py +0 -0
  223. {dycw_utilities-0.125.12 → dycw_utilities-0.125.14}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.125.12
3
+ Version: 0.125.14
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -93,7 +93,7 @@ dependencies = [
93
93
  name = "dycw-utilities"
94
94
  readme = "README.md"
95
95
  requires-python = ">= 3.12"
96
- version = "0.125.12"
96
+ version = "0.125.14"
97
97
 
98
98
  [project.optional-dependencies]
99
99
  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.125.12"
337
+ current_version = "0.125.14"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -975,6 +975,12 @@ class TestLooper:
975
975
  looper.put_left_nowait(None)
976
976
  assert not looper.empty()
977
977
 
978
+ def test_get_all_nowait(self) -> None:
979
+ looper = _ExampleLooper()
980
+ looper.put_left_nowait(None)
981
+ items = looper.get_all_nowait()
982
+ assert items == [None]
983
+
978
984
  async def test_initialize_already_initializing(
979
985
  self, *, caplog: LogCaptureFixture
980
986
  ) -> None:
@@ -982,14 +988,20 @@ class TestLooper:
982
988
  @override
983
989
  async def _initialize_core(self) -> None:
984
990
  if self._initialization_attempts == 1:
985
- _ = await super().initialize()
991
+ _ = await super().initialize(sleep_if_failure=False)
986
992
  await super()._initialize_core()
987
993
 
988
994
  looper = Example()
989
- _ = await looper.initialize()
995
+ _ = await looper.initialize(sleep_if_failure=False)
990
996
  _ = one(m for m in caplog.messages if search(": already initializing$", m))
991
997
 
992
- async def test_initialize_failure(self, *, caplog: LogCaptureFixture) -> None:
998
+ @given(case=sampled_from([(True, "; sleeping for .*"), (False, "")]))
999
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
1000
+ async def test_initialize_failure(
1001
+ self, *, case: tuple[bool, str], caplog: LogCaptureFixture
1002
+ ) -> None:
1003
+ sleep_if_failure, extra = case
1004
+
993
1005
  class Example(_ExampleLooper):
994
1006
  @override
995
1007
  async def _initialize_core(self) -> None:
@@ -998,11 +1010,13 @@ class TestLooper:
998
1010
  await super()._initialize_core()
999
1011
 
1000
1012
  looper = Example()
1001
- _ = await looper.initialize()
1013
+ _ = await looper.initialize(sleep_if_failure=sleep_if_failure)
1002
1014
  _ = one(
1003
1015
  m
1004
1016
  for m in caplog.messages
1005
- if search(r": encountered _ExampleLooperError\(\) whilst initializing$", m)
1017
+ if search(
1018
+ rf": encountered _ExampleLooperError\(\) whilst initializing{extra}$", m
1019
+ )
1006
1020
  )
1007
1021
 
1008
1022
  def test_len_and_qsize(self) -> None:
@@ -1122,9 +1136,13 @@ class TestLooper:
1122
1136
  if search(r": already requested stop when empty$", m)
1123
1137
  )
1124
1138
 
1139
+ @given(case=sampled_from([(True, "; sleeping for .*"), (False, "")]))
1140
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
1125
1141
  async def test_restart_failure_during_initialization(
1126
- self, *, caplog: LogCaptureFixture
1142
+ self, *, case: tuple[bool, str], caplog: LogCaptureFixture
1127
1143
  ) -> None:
1144
+ sleep_if_failure, extra = case
1145
+
1128
1146
  class Example(_ExampleLooper):
1129
1147
  @override
1130
1148
  async def _initialize_core(self) -> None:
@@ -1133,19 +1151,23 @@ class TestLooper:
1133
1151
  await super()._initialize_core()
1134
1152
 
1135
1153
  looper = Example()
1136
- await looper.restart()
1154
+ await looper.restart(sleep_if_failure=sleep_if_failure)
1137
1155
  _ = one(
1138
1156
  m
1139
1157
  for m in caplog.messages
1140
1158
  if search(
1141
- r": encountered _ExampleLooperError\(\) whilst restarting, during initialization$",
1159
+ rf": encountered _ExampleLooperError\(\) whilst restarting \(initialize\){extra}$",
1142
1160
  m,
1143
1161
  )
1144
1162
  )
1145
1163
 
1164
+ @given(case=sampled_from([(True, "; sleeping for .*"), (False, "")]))
1165
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
1146
1166
  async def test_restart_failure_during_tear_down(
1147
- self, *, caplog: LogCaptureFixture
1167
+ self, *, case: tuple[bool, str], caplog: LogCaptureFixture
1148
1168
  ) -> None:
1169
+ sleep_if_failure, extra = case
1170
+
1149
1171
  class Example(_ExampleLooper):
1150
1172
  @override
1151
1173
  async def _tear_down_core(self) -> None:
@@ -1154,19 +1176,23 @@ class TestLooper:
1154
1176
  await super()._tear_down_core()
1155
1177
 
1156
1178
  looper = Example()
1157
- await looper.restart()
1179
+ await looper.restart(sleep_if_failure=sleep_if_failure)
1158
1180
  _ = one(
1159
1181
  m
1160
1182
  for m in caplog.messages
1161
1183
  if search(
1162
- r": encountered _ExampleLooperError\(\) whilst restarting, during tear down$",
1184
+ rf": encountered _ExampleLooperError\(\) whilst restarting \(tear down\){extra}$",
1163
1185
  m,
1164
1186
  )
1165
1187
  )
1166
1188
 
1189
+ @given(case=sampled_from([(True, "; sleeping for .*"), (False, "")]))
1190
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
1167
1191
  async def test_restart_failure_during_tear_down_and_initialization(
1168
- self, *, caplog: LogCaptureFixture
1192
+ self, *, case: tuple[bool, str], caplog: LogCaptureFixture
1169
1193
  ) -> None:
1194
+ sleep_if_failure, extra = case
1195
+
1170
1196
  class Example(_ExampleLooper):
1171
1197
  @override
1172
1198
  async def _initialize_core(self) -> None:
@@ -1181,12 +1207,12 @@ class TestLooper:
1181
1207
  await super()._tear_down_core()
1182
1208
 
1183
1209
  looper = Example()
1184
- await looper.restart()
1210
+ await looper.restart(sleep_if_failure=sleep_if_failure)
1185
1211
  _ = one(
1186
1212
  m
1187
1213
  for m in caplog.messages
1188
1214
  if search(
1189
- r": encountered _ExampleLooperError\(\) \(tear down\) and then _ExampleLooperError\(\) \(initialization\) whilst restarting$",
1215
+ rf": encountered _ExampleLooperError\(\) \(tear down\) and then _ExampleLooperError\(\) \(initialization\) whilst restarting{extra}$",
1190
1216
  m,
1191
1217
  )
1192
1218
  )
@@ -1221,21 +1247,27 @@ class TestLooper:
1221
1247
  )
1222
1248
  )
1223
1249
 
1224
- async def test_tear_down_already_initializing(
1250
+ async def test_tear_down_already_tearing_down(
1225
1251
  self, *, caplog: LogCaptureFixture
1226
1252
  ) -> None:
1227
1253
  class Example(_ExampleLooper):
1228
1254
  @override
1229
1255
  async def _tear_down_core(self) -> None:
1230
1256
  if self._tear_down_attempts == 1:
1231
- _ = await super().tear_down()
1257
+ _ = await super().tear_down(sleep_if_failure=False)
1232
1258
  await super()._tear_down_core()
1233
1259
 
1234
1260
  looper = Example()
1235
- _ = await looper.tear_down()
1261
+ _ = await looper.tear_down(sleep_if_failure=False)
1236
1262
  _ = one(m for m in caplog.messages if search(": already tearing down$", m))
1237
1263
 
1238
- async def test_tear_down_failure(self, *, caplog: LogCaptureFixture) -> None:
1264
+ @given(case=sampled_from([(True, "; sleeping for .*"), (False, "")]))
1265
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
1266
+ async def test_tear_down_failure(
1267
+ self, *, case: tuple[bool, str], caplog: LogCaptureFixture
1268
+ ) -> None:
1269
+ sleep_if_failure, extra = case
1270
+
1239
1271
  class Example(_ExampleLooper):
1240
1272
  @override
1241
1273
  async def _tear_down_core(self) -> None:
@@ -1244,11 +1276,13 @@ class TestLooper:
1244
1276
  await super()._tear_down_core()
1245
1277
 
1246
1278
  looper = Example()
1247
- _ = await looper.tear_down()
1279
+ _ = await looper.tear_down(sleep_if_failure=sleep_if_failure)
1248
1280
  _ = one(
1249
1281
  m
1250
1282
  for m in caplog.messages
1251
- if search(r": encountered _ExampleLooperError\(\) whilst tearing down$", m)
1283
+ if search(
1284
+ rf": encountered _ExampleLooperError\(\) whilst tearing down{extra}$", m
1285
+ )
1252
1286
  )
1253
1287
 
1254
1288
  def _assert_stats(
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.125.12"
3
+ __version__ = "0.125.14"
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
5
5
  from asyncio import (
6
6
  CancelledError,
7
7
  Event,
8
+ Lock,
8
9
  PriorityQueue,
9
10
  Queue,
10
11
  QueueEmpty,
@@ -719,6 +720,7 @@ class Looper(Generic[_T]):
719
720
  _is_stopped: Event = field(default_factory=Event, init=False, repr=False)
720
721
  _is_tearing_down: Event = field(default_factory=Event, init=False, repr=False)
721
722
  # internal objects
723
+ _lock: Lock = field(default_factory=Lock, init=False, repr=False, hash=False)
722
724
  _logger: Logger = field(init=False, repr=False, hash=False)
723
725
  _queue: EnhancedQueue[_T] = field(
724
726
  default_factory=EnhancedQueue, init=False, repr=False, hash=False
@@ -742,8 +744,9 @@ class Looper(Generic[_T]):
742
744
  case False:
743
745
  _ = self._debug and self._logger.debug("%s: entering context...", self)
744
746
  self._is_entered.set()
745
- self._entries += 1
746
- self._task = create_task(self.run_looper())
747
+ async with self._lock:
748
+ self._entries += 1
749
+ self._task = create_task(self.run_looper())
747
750
  for looper in self._yield_sub_loopers():
748
751
  _ = self._debug and self._logger.debug(
749
752
  "%s: adding sub-looper %s", self, looper
@@ -752,7 +755,8 @@ class Looper(Generic[_T]):
752
755
  self._logger.warning(
753
756
  "%s: changing sub-looper %s to auto-start...", self, looper
754
757
  )
755
- looper.auto_start = True
758
+ async with self._lock:
759
+ looper.auto_start = True
756
760
  _ = await self._stack.enter_async_context(looper)
757
761
  if self.auto_start:
758
762
  _ = self._debug and self._logger.debug("%s: auto-starting...", self)
@@ -797,9 +801,6 @@ class Looper(Generic[_T]):
797
801
  case Task() as task:
798
802
  return task.__await__()
799
803
  case _ as never:
800
- self._logger.warning( # pragma: no cover
801
- "Got %s of type %s", self._task, type(self._task)
802
- )
803
804
  assert_never(never)
804
805
 
805
806
  def __len__(self) -> int:
@@ -812,6 +813,10 @@ class Looper(Generic[_T]):
812
813
  """Check if the queue is empty."""
813
814
  return self._queue.empty()
814
815
 
816
+ def get_all_nowait(self, *, reverse: bool = False) -> Sequence[_T]:
817
+ """Remove and return all items from the queue without blocking."""
818
+ return self._queue.get_all_nowait(reverse=reverse)
819
+
815
820
  def get_left_nowait(self) -> _T:
816
821
  """Remove and return an item from the start of the queue without blocking."""
817
822
  return self._queue.get_left_nowait()
@@ -820,7 +825,7 @@ class Looper(Generic[_T]):
820
825
  """Remove and return an item from the end of the queue without blocking."""
821
826
  return self._queue.get_right_nowait()
822
827
 
823
- async def initialize(self) -> Exception | None:
828
+ async def initialize(self, *, sleep_if_failure: bool) -> Exception | None:
824
829
  """Initialize the looper."""
825
830
  match self._is_initializing.is_set():
826
831
  case True:
@@ -830,23 +835,38 @@ class Looper(Generic[_T]):
830
835
  _ = self._debug and self._logger.debug("%s: initializing...", self)
831
836
  self._is_initializing.set()
832
837
  self._is_initialized.clear()
833
- self._initialization_attempts += 1
838
+ async with self._lock:
839
+ self._initialization_attempts += 1
834
840
  try:
835
841
  await self._initialize_core()
836
842
  except Exception as error: # noqa: BLE001
837
- _ = self._logger.warning(
838
- "%s: encountered %s whilst initializing",
839
- self,
840
- repr_error(error),
841
- )
842
- self._initialization_failures += 1
843
+ async with self._lock:
844
+ self._initialization_failures += 1
843
845
  ret = error
846
+ match sleep_if_failure:
847
+ case True:
848
+ _ = self._logger.warning(
849
+ "%s: encountered %s whilst initializing; sleeping for %s...",
850
+ self,
851
+ repr_error(error),
852
+ self.backoff,
853
+ )
854
+ await sleep(self._backoff)
855
+ case False:
856
+ _ = self._logger.warning(
857
+ "%s: encountered %s whilst initializing",
858
+ self,
859
+ repr_error(error),
860
+ )
861
+ case _ as never:
862
+ assert_never(never)
844
863
  else:
845
864
  _ = self._debug and self._logger.debug(
846
865
  "%s: finished initializing", self
847
866
  )
848
867
  self._is_initialized.set()
849
- self._initialization_successes += 1
868
+ async with self._lock:
869
+ self._initialization_successes += 1
850
870
  ret = None
851
871
  finally:
852
872
  self._is_initializing.clear()
@@ -931,39 +951,75 @@ class Looper(Generic[_T]):
931
951
  case _ as never:
932
952
  assert_never(never)
933
953
 
934
- async def restart(self) -> None:
954
+ async def restart(self, *, sleep_if_failure: bool) -> None:
935
955
  """Restart the looper."""
936
956
  _ = self._debug and self._logger.debug("%s: restarting...", self)
937
957
  self._is_pending_restart.clear()
938
- self._restart_attempts += 1
939
- tear_down = await self.tear_down()
940
- initialization = await self.initialize()
941
- match tear_down, initialization:
942
- case None, None:
958
+ async with self._lock:
959
+ self._restart_attempts += 1
960
+ tear_down = await self.tear_down(sleep_if_failure=False)
961
+ initialization = await self.initialize(sleep_if_failure=False)
962
+ match tear_down, initialization, sleep_if_failure:
963
+ case None, None, bool():
943
964
  _ = self._debug and self._logger.debug("%s: finished restarting", self)
944
- self._restart_successes += 1
945
- case Exception(), None:
965
+ async with self._lock:
966
+ self._restart_successes += 1
967
+ case Exception(), None, True:
968
+ async with self._lock:
969
+ self._restart_failures += 1
970
+ _ = self._logger.warning(
971
+ "%s: encountered %s whilst restarting (tear down); sleeping for %s...",
972
+ self,
973
+ repr_error(tear_down),
974
+ self.backoff,
975
+ )
976
+ await sleep(self._backoff)
977
+ case Exception(), None, False:
978
+ async with self._lock:
979
+ self._restart_failures += 1
946
980
  _ = self._logger.warning(
947
- "%s: encountered %s whilst restarting, during tear down",
981
+ "%s: encountered %s whilst restarting (tear down)",
948
982
  self,
949
983
  repr_error(tear_down),
950
984
  )
951
- self._restart_failures += 1
952
- case None, Exception():
985
+ case None, Exception(), True:
986
+ async with self._lock:
987
+ self._restart_failures += 1
953
988
  _ = self._logger.warning(
954
- "%s: encountered %s whilst restarting, during initialization",
989
+ "%s: encountered %s whilst restarting (initialize); sleeping for %s...",
955
990
  self,
956
991
  repr_error(initialization),
992
+ self.backoff,
957
993
  )
958
- self._restart_failures += 1
959
- case Exception(), Exception():
994
+ await sleep(self._backoff)
995
+ case None, Exception(), False:
996
+ async with self._lock:
997
+ self._restart_failures += 1
998
+ _ = self._logger.warning(
999
+ "%s: encountered %s whilst restarting (initialize)",
1000
+ self,
1001
+ repr_error(initialization),
1002
+ )
1003
+ case Exception(), Exception(), True:
1004
+ async with self._lock:
1005
+ self._restart_failures += 1
1006
+ _ = self._logger.warning(
1007
+ "%s: encountered %s (tear down) and then %s (initialization) whilst restarting; sleeping for %s...",
1008
+ self,
1009
+ repr_error(tear_down),
1010
+ repr_error(initialization),
1011
+ self.backoff,
1012
+ )
1013
+ await sleep(self._backoff)
1014
+ case Exception(), Exception(), False:
1015
+ async with self._lock:
1016
+ self._restart_failures += 1
960
1017
  _ = self._logger.warning(
961
1018
  "%s: encountered %s (tear down) and then %s (initialization) whilst restarting",
962
1019
  self,
963
1020
  repr_error(tear_down),
964
1021
  repr_error(initialization),
965
1022
  )
966
- self._restart_failures += 1
967
1023
  case _ as never:
968
1024
  assert_never(never)
969
1025
 
@@ -979,12 +1035,13 @@ class Looper(Generic[_T]):
979
1035
  ):
980
1036
  await self.stop()
981
1037
  elif self._is_pending_restart.is_set():
982
- await self.restart()
1038
+ await self.restart(sleep_if_failure=True)
983
1039
  elif not self._is_initialized.is_set():
984
- _ = await self.initialize()
1040
+ _ = await self.initialize(sleep_if_failure=True)
985
1041
  else:
986
1042
  _ = self._debug and self._logger.debug("%s: running core...", self)
987
- self._core_attempts += 1
1043
+ async with self._lock:
1044
+ self._core_attempts += 1
988
1045
  try:
989
1046
  await self.core()
990
1047
  except Exception as error: # noqa: BLE001
@@ -993,11 +1050,13 @@ class Looper(Generic[_T]):
993
1050
  self,
994
1051
  repr_error(error),
995
1052
  )
996
- self._core_failures += 1
1053
+ async with self._lock:
1054
+ self._core_failures += 1
997
1055
  self.request_restart()
998
1056
  await sleep(self._backoff)
999
1057
  else:
1000
- self._core_successes += 1
1058
+ async with self._lock:
1059
+ self._core_successes += 1
1001
1060
  await sleep(self._freq)
1002
1061
 
1003
1062
  @property
@@ -1029,12 +1088,13 @@ class Looper(Generic[_T]):
1029
1088
  _ = self._debug and self._logger.debug("%s: stopping...", self)
1030
1089
  self._is_pending_stop.clear()
1031
1090
  self._is_stopped.set()
1032
- self._stops += 1
1091
+ async with self._lock:
1092
+ self._stops += 1
1033
1093
  _ = self._debug and self._logger.debug("%s: stopped", self)
1034
1094
  case _ as never:
1035
1095
  assert_never(never)
1036
1096
 
1037
- async def tear_down(self) -> Exception | None:
1097
+ async def tear_down(self, *, sleep_if_failure: bool) -> Exception | None:
1038
1098
  """Tear down the looper."""
1039
1099
  match self._is_tearing_down.is_set():
1040
1100
  case True:
@@ -1043,22 +1103,37 @@ class Looper(Generic[_T]):
1043
1103
  case False:
1044
1104
  _ = self._debug and self._logger.debug("%s: tearing down...", self)
1045
1105
  self._is_tearing_down.set()
1046
- self._tear_down_attempts += 1
1106
+ async with self._lock:
1107
+ self._tear_down_attempts += 1
1047
1108
  try:
1048
1109
  await self._tear_down_core()
1049
1110
  except Exception as error: # noqa: BLE001
1050
- _ = self._logger.warning(
1051
- "%s: encountered %s whilst tearing down",
1052
- self,
1053
- repr_error(error),
1054
- )
1055
- self._tear_down_failures += 1
1111
+ async with self._lock:
1112
+ self._tear_down_failures += 1
1056
1113
  ret = error
1114
+ match sleep_if_failure:
1115
+ case True:
1116
+ _ = self._logger.warning(
1117
+ "%s: encountered %s whilst tearing down; sleeping for %s...",
1118
+ self,
1119
+ repr_error(error),
1120
+ self.backoff,
1121
+ )
1122
+ await sleep(self._backoff)
1123
+ case False:
1124
+ _ = self._logger.warning(
1125
+ "%s: encountered %s whilst tearing down",
1126
+ self,
1127
+ repr_error(error),
1128
+ )
1129
+ case _ as never:
1130
+ assert_never(never)
1057
1131
  else:
1058
1132
  _ = self._debug and self._logger.debug(
1059
1133
  "%s: finished tearing down", self
1060
1134
  )
1061
- self._tear_down_successes += 1
1135
+ async with self._lock:
1136
+ self._tear_down_successes += 1
1062
1137
  ret = None
1063
1138
  finally:
1064
1139
  self._is_tearing_down.clear()