dycw-utilities 0.125.23__tar.gz → 0.125.24__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 (227) hide show
  1. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/PKG-INFO +1 -1
  2. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/pyproject.toml +2 -2
  3. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_asyncio.py +84 -60
  4. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/__init__.py +1 -1
  5. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/asyncio.py +12 -0
  6. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/.gitignore +0 -0
  7. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/LICENSE +0 -0
  8. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/README.md +0 -0
  9. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/__init__.py +0 -0
  10. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/conftest.py +0 -0
  11. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/__init__.py +0 -0
  12. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_missing/__init__.py +0 -0
  13. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_missing/module.py +0 -0
  14. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/__init__.py +0 -0
  15. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/outer_1.py +0 -0
  16. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/outer_2.py +0 -0
  17. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  18. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  19. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  20. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  21. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_without/__init__.py +0 -0
  22. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_without/module_1.py +0 -0
  23. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/package_without/module_2.py +0 -0
  24. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/standalone.py +0 -0
  25. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/modules/with_imports.py +0 -0
  26. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  27. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  28. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  29. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  30. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  31. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  32. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  33. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  34. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_altair.py +0 -0
  35. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_atomicwrites.py +0 -0
  36. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_atools.py +0 -0
  37. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_cachetools.py +0 -0
  38. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_click.py +0 -0
  39. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_concurrent.py +0 -0
  40. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_contextlib.py +0 -0
  41. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_contextvars.py +0 -0
  42. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_cryptography.py +0 -0
  43. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_cvxpy.py +0 -0
  44. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_dataclasses.py +0 -0
  45. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_datetime.py +0 -0
  46. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_enum.py +0 -0
  47. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_errors.py +0 -0
  48. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_eventkit.py +0 -0
  49. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_fastapi.py +0 -0
  50. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_fpdf2.py +0 -0
  51. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_functions.py +0 -0
  52. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_functools.py +0 -0
  53. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_getpass.py +0 -0
  54. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_git.py +0 -0
  55. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_hashlib.py +0 -0
  56. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_http.py +0 -0
  57. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_hypothesis.py +0 -0
  58. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_importlib.py +0 -0
  59. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_ipython.py +0 -0
  60. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_iterables.py +0 -0
  61. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_jupyter.py +0 -0
  62. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_libcst.py +0 -0
  63. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_lightweight_charts.py +0 -0
  64. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_logging.py +0 -0
  65. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_loguru.py +0 -0
  66. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_luigi.py +0 -0
  67. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_math.py +0 -0
  68. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_memory_profiler.py +0 -0
  69. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_modules.py +0 -0
  70. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_more_itertools.py +0 -0
  71. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_numpy.py +0 -0
  72. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_operator.py +0 -0
  73. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_optuna.py +0 -0
  74. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_orjson.py +0 -0
  75. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_os.py +0 -0
  76. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_parse.py +0 -0
  77. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pathlib.py +0 -0
  78. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_period.py +0 -0
  79. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pickle.py +0 -0
  80. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_platform.py +0 -0
  81. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_polars.py +0 -0
  82. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_polars_ols.py +0 -0
  83. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pqdm.py +0 -0
  84. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_psutil.py +0 -0
  85. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pydantic.py +0 -0
  86. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pyinstrument.py +0 -0
  87. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pyrsistent.py +0 -0
  88. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pytest.py +0 -0
  89. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_pytest_regressions.py +0 -0
  90. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_python_dotenv.py +0 -0
  91. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_random.py +0 -0
  92. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_re.py +0 -0
  93. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_redis.py +0 -0
  94. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_reprlib.py +0 -0
  95. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_rich.py +0 -0
  96. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_scipy.py +0 -0
  97. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_sentinel.py +0 -0
  98. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_shelve.py +0 -0
  99. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_slack_sdk.py +0 -0
  100. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_socket.py +0 -0
  101. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_sqlalchemy.py +0 -0
  102. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_sqlalchemy_polars.py +0 -0
  103. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_statsmodel.py +0 -0
  104. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_streamlit.py +0 -0
  105. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_string.py +0 -0
  106. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_sys.py +0 -0
  107. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_tempfile.py +0 -0
  108. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_tenacity.py +0 -0
  109. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_text.py +0 -0
  110. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_threading.py +0 -0
  111. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_timer.py +0 -0
  112. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback.py +0 -0
  113. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/__init__.py +0 -0
  114. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/chain.py +0 -0
  115. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  116. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  117. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  118. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/many.py +0 -0
  119. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/one.py +0 -0
  120. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/recursive.py +0 -0
  121. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  122. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  123. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/two.py +0 -0
  124. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_traceback_funcs/untraced.py +0 -0
  125. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_types.py +0 -0
  126. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_typing.py +0 -0
  127. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_typing_funcs/__init__.py +0 -0
  128. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_typing_funcs/no_future.py +0 -0
  129. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_typing_funcs/with_future.py +0 -0
  130. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_tzdata.py +0 -0
  131. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_tzlocal.py +0 -0
  132. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_uuid.py +0 -0
  133. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_version.py +0 -0
  134. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_warnings.py +0 -0
  135. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_whenever.py +0 -0
  136. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_zipfile.py +0 -0
  137. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/tests/test_zoneinfo.py +0 -0
  138. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/altair.py +0 -0
  139. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/atomicwrites.py +0 -0
  140. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/atools.py +0 -0
  141. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/cachetools.py +0 -0
  142. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/click.py +0 -0
  143. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/concurrent.py +0 -0
  144. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/contextlib.py +0 -0
  145. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/contextvars.py +0 -0
  146. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/cryptography.py +0 -0
  147. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/cvxpy.py +0 -0
  148. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/dataclasses.py +0 -0
  149. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/datetime.py +0 -0
  150. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/enum.py +0 -0
  151. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/errors.py +0 -0
  152. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/eventkit.py +0 -0
  153. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/fastapi.py +0 -0
  154. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/fpdf2.py +0 -0
  155. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/functions.py +0 -0
  156. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/functools.py +0 -0
  157. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/getpass.py +0 -0
  158. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/git.py +0 -0
  159. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/hashlib.py +0 -0
  160. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/http.py +0 -0
  161. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/hypothesis.py +0 -0
  162. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/importlib.py +0 -0
  163. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/ipython.py +0 -0
  164. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/iterables.py +0 -0
  165. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/jupyter.py +0 -0
  166. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/libcst.py +0 -0
  167. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/lightweight_charts.py +0 -0
  168. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/logging.py +0 -0
  169. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/loguru.py +0 -0
  170. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/luigi.py +0 -0
  171. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/math.py +0 -0
  172. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/memory_profiler.py +0 -0
  173. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/modules.py +0 -0
  174. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/more_itertools.py +0 -0
  175. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/numpy.py +0 -0
  176. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/operator.py +0 -0
  177. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/optuna.py +0 -0
  178. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/orjson.py +0 -0
  179. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/os.py +0 -0
  180. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/parse.py +0 -0
  181. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pathlib.py +0 -0
  182. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/period.py +0 -0
  183. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pickle.py +0 -0
  184. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/platform.py +0 -0
  185. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/polars.py +0 -0
  186. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/polars_ols.py +0 -0
  187. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pqdm.py +0 -0
  188. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/psutil.py +0 -0
  189. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/py.typed +0 -0
  190. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pydantic.py +0 -0
  191. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pyinstrument.py +0 -0
  192. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pyrsistent.py +0 -0
  193. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pytest.py +0 -0
  194. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/pytest_regressions.py +0 -0
  195. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/python_dotenv.py +0 -0
  196. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/random.py +0 -0
  197. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/re.py +0 -0
  198. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/redis.py +0 -0
  199. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/reprlib.py +0 -0
  200. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/rich.py +0 -0
  201. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/scipy.py +0 -0
  202. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/sentinel.py +0 -0
  203. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/shelve.py +0 -0
  204. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/slack_sdk.py +0 -0
  205. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/socket.py +0 -0
  206. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/sqlalchemy.py +0 -0
  207. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/sqlalchemy_polars.py +0 -0
  208. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/statsmodels.py +0 -0
  209. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/streamlit.py +0 -0
  210. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/string.py +0 -0
  211. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/sys.py +0 -0
  212. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/tempfile.py +0 -0
  213. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/tenacity.py +0 -0
  214. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/text.py +0 -0
  215. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/threading.py +0 -0
  216. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/timer.py +0 -0
  217. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/traceback.py +0 -0
  218. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/types.py +0 -0
  219. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/typing.py +0 -0
  220. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/tzdata.py +0 -0
  221. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/tzlocal.py +0 -0
  222. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/uuid.py +0 -0
  223. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/version.py +0 -0
  224. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/warnings.py +0 -0
  225. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/whenever.py +0 -0
  226. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/src/utilities/zipfile.py +0 -0
  227. {dycw_utilities-0.125.23 → dycw_utilities-0.125.24}/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.23
3
+ Version: 0.125.24
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.125.23"
97
+ version = "0.125.24"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
@@ -335,7 +335,7 @@ zzz-test-zoneinfo = [
335
335
  # bump-my-version
336
336
  [tool.bumpversion]
337
337
  allow_dirty = true
338
- current_version = "0.125.23"
338
+ current_version = "0.125.24"
339
339
 
340
340
  [[tool.bumpversion.files]]
341
341
  filename = "src/utilities/__init__.py"
@@ -56,6 +56,7 @@ from utilities.datetime import (
56
56
  datetime_duration_to_timedelta,
57
57
  get_now,
58
58
  )
59
+ from utilities.functions import get_class_name
59
60
  from utilities.hypothesis import sentinels, text_ascii
60
61
  from utilities.iterables import one, unique_everseen
61
62
  from utilities.pytest import skipif_windows
@@ -882,7 +883,7 @@ class _ExampleLooperError(Exception): ...
882
883
 
883
884
 
884
885
  @dataclass(kw_only=True)
885
- class _ExampleLooper(Looper[Any]):
886
+ class _ExampleCounterLooper(Looper[Any]):
886
887
  freq: Duration = field(default=10 * MILLISECOND, repr=False)
887
888
  backoff: Duration = field(default=100 * MILLISECOND, repr=False)
888
889
  _debug: bool = field(default=True, repr=False)
@@ -901,13 +902,13 @@ class _ExampleLooper(Looper[Any]):
901
902
 
902
903
 
903
904
  @dataclass(kw_only=True)
904
- class _ExampleOuterLooper(_ExampleLooper):
905
- inner: _ExampleLooper = field(init=False, repr=False)
905
+ class _ExampleOuterLooper(_ExampleCounterLooper):
906
+ inner: _ExampleCounterLooper = field(init=False, repr=False)
906
907
 
907
908
  @override
908
909
  def __post_init__(self) -> None:
909
910
  super().__post_init__()
910
- self.inner = _ExampleLooper(
911
+ self.inner = _ExampleCounterLooper(
911
912
  freq=self.freq / 2,
912
913
  backoff=self.backoff / 2,
913
914
  logger=self.logger,
@@ -921,6 +922,15 @@ class _ExampleOuterLooper(_ExampleLooper):
921
922
  yield self.inner
922
923
 
923
924
 
925
+ @dataclass(kw_only=True)
926
+ class _ExampleQueueLooper(Looper[int]):
927
+ @override
928
+ async def core(self) -> None:
929
+ await super().core()
930
+ if not self.empty():
931
+ _ = self.get_left_nowait()
932
+
933
+
924
934
  class TestLooper:
925
935
  sleep_if_failure_cases: ClassVar[list[Any]] = [
926
936
  param(True, "; sleeping for .*"),
@@ -928,61 +938,60 @@ class TestLooper:
928
938
  ]
929
939
 
930
940
  async def test_main_with_nothing(self) -> None:
931
- looper = _ExampleLooper()
941
+ looper = _ExampleCounterLooper()
932
942
  async with looper:
933
943
  ...
934
944
  assert looper.stats == _LooperStats(entries=1, stops=1)
935
945
 
936
- @mark.flaky
937
- async def test_main_with_explicit_start(self) -> None:
938
- looper = _ExampleLooper()
939
- with raises(TimeoutError):
940
- async with timeout(1.0), looper:
941
- await looper
942
- self._assert_stats(looper, stops=1)
943
-
944
- async def test_main_with_auto_start(self) -> None:
945
- looper = _ExampleLooper(auto_start=True)
946
+ async def test_auto_start(self) -> None:
947
+ looper = _ExampleCounterLooper(auto_start=True)
946
948
  with raises(TimeoutError):
947
949
  async with timeout(1.0), looper:
948
950
  ...
949
951
  self._assert_stats(looper)
950
952
 
951
- @mark.flaky
952
- async def test_main_with_timeout(self) -> None:
953
- looper = _ExampleLooper(timeout=SECOND)
954
- async with looper:
955
- with raises(LooperTimeoutError, match="Timeout"):
956
- await looper
957
- self._assert_stats(looper, stops=1)
958
-
959
- async def test_main_with_auto_start_and_timeout(self) -> None:
960
- looper = _ExampleLooper(auto_start=True, timeout=SECOND)
953
+ async def test_auto_start_and_timeout(self) -> None:
954
+ looper = _ExampleCounterLooper(auto_start=True, timeout=1.0)
961
955
  async with looper:
962
956
  ...
963
957
  self._assert_stats(looper, stops=1)
964
958
 
965
959
  async def test_await_without_task(self) -> None:
966
- looper = _ExampleLooper()
960
+ looper = _ExampleCounterLooper()
967
961
  with raises(_LooperNoTaskError, match=".* has no running task"):
968
962
  await looper
969
963
 
970
964
  async def test_context_manager_already_entered(
971
965
  self, *, caplog: LogCaptureFixture
972
966
  ) -> None:
973
- looper = _ExampleLooper(auto_start=True, timeout=SECOND)
967
+ looper = _ExampleCounterLooper(auto_start=True, timeout=SECOND)
974
968
  async with looper, looper:
975
969
  ...
976
970
  _ = one(m for m in caplog.messages if search(": already entered$", m))
977
971
 
978
972
  def test_empty(self) -> None:
979
- looper = _ExampleLooper()
973
+ looper = _ExampleCounterLooper()
980
974
  assert looper.empty()
981
975
  looper.put_left_nowait(None)
982
976
  assert not looper.empty()
983
977
 
978
+ async def test_empty_upon_exit(self) -> None:
979
+ looper = _ExampleQueueLooper(freq=0.05, empty_upon_exit=True)
980
+ looper.put_right_nowait(0)
981
+ assert not looper.empty()
982
+ async with timeout(1.0), looper:
983
+ ...
984
+ assert looper.empty()
985
+
986
+ async def test_explicit_start(self) -> None:
987
+ looper = _ExampleCounterLooper()
988
+ with raises(TimeoutError):
989
+ async with timeout(1.0), looper:
990
+ await looper
991
+ self._assert_stats(looper, stops=1)
992
+
984
993
  def test_get_all_nowait(self) -> None:
985
- looper = _ExampleLooper()
994
+ looper = _ExampleCounterLooper()
986
995
  looper.put_left_nowait(None)
987
996
  items = looper.get_all_nowait()
988
997
  assert items == [None]
@@ -990,7 +999,7 @@ class TestLooper:
990
999
  async def test_initialize_already_initializing(
991
1000
  self, *, caplog: LogCaptureFixture
992
1001
  ) -> None:
993
- class Example(_ExampleLooper):
1002
+ class Example(_ExampleCounterLooper):
994
1003
  @override
995
1004
  async def _initialize_core(self) -> None:
996
1005
  if self._initialization_attempts == 1:
@@ -1005,7 +1014,7 @@ class TestLooper:
1005
1014
  async def test_initialize_failure(
1006
1015
  self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
1007
1016
  ) -> None:
1008
- class Example(_ExampleLooper):
1017
+ class Example(_ExampleCounterLooper):
1009
1018
  @override
1010
1019
  async def _initialize_core(self) -> None:
1011
1020
  if self._initialization_attempts == 1:
@@ -1018,17 +1027,17 @@ class TestLooper:
1018
1027
  _ = one(m for m in caplog.messages if search(pattern, m))
1019
1028
 
1020
1029
  def test_len_and_qsize(self) -> None:
1021
- looper = _ExampleLooper()
1030
+ looper = _ExampleCounterLooper()
1022
1031
  assert len(looper) == looper.qsize() == 0
1023
1032
  looper.put_left_nowait(None)
1024
1033
  assert len(looper) == looper.qsize() == 1
1025
1034
 
1026
1035
  def test_replace(self) -> None:
1027
- looper = _ExampleLooper().replace(freq=SECOND)
1028
- assert looper.freq == SECOND
1036
+ looper = _ExampleCounterLooper().replace(freq=10.0)
1037
+ assert looper.freq == 10.0
1029
1038
 
1030
1039
  async def test_request_restart(self) -> None:
1031
- class Example(_ExampleLooper):
1040
+ class Example(_ExampleCounterLooper):
1032
1041
  @override
1033
1042
  async def core(self) -> None:
1034
1043
  await super().core()
@@ -1037,7 +1046,7 @@ class TestLooper:
1037
1046
  ):
1038
1047
  self.request_restart()
1039
1048
 
1040
- looper = Example(auto_start=True, timeout=SECOND)
1049
+ looper = Example(auto_start=True, timeout=1.0)
1041
1050
  async with looper:
1042
1051
  ...
1043
1052
  self._assert_stats(
@@ -1053,7 +1062,7 @@ class TestLooper:
1053
1062
  def test_request_restart_already_requested(
1054
1063
  self, *, caplog: LogCaptureFixture
1055
1064
  ) -> None:
1056
- looper = _ExampleLooper()
1065
+ looper = _ExampleCounterLooper()
1057
1066
  for _ in range(2):
1058
1067
  looper.request_restart()
1059
1068
  _ = one(
@@ -1061,7 +1070,7 @@ class TestLooper:
1061
1070
  )
1062
1071
 
1063
1072
  async def test_request_stop(self) -> None:
1064
- class Example(_ExampleLooper):
1073
+ class Example(_ExampleCounterLooper):
1065
1074
  @override
1066
1075
  async def core(self) -> None:
1067
1076
  await super().core()
@@ -1070,7 +1079,7 @@ class TestLooper:
1070
1079
  ):
1071
1080
  self.request_stop()
1072
1081
 
1073
- looper = Example(auto_start=True, timeout=SECOND)
1082
+ looper = Example(auto_start=True, timeout=1.0)
1074
1083
  async with looper:
1075
1084
  ...
1076
1085
  self._assert_stats(
@@ -1084,13 +1093,13 @@ class TestLooper:
1084
1093
  )
1085
1094
 
1086
1095
  def test_request_stop_already_requested(self, *, caplog: LogCaptureFixture) -> None:
1087
- looper = _ExampleLooper()
1096
+ looper = _ExampleCounterLooper()
1088
1097
  for _ in range(2):
1089
1098
  looper.request_stop()
1090
1099
  _ = one(m for m in caplog.messages if search(r": already requested stop$", m))
1091
1100
 
1092
1101
  async def test_request_stop_when_empty(self) -> None:
1093
- class Example(_ExampleLooper):
1102
+ class Example(_ExampleCounterLooper):
1094
1103
  @override
1095
1104
  async def core(self) -> None:
1096
1105
  await super().core()
@@ -1103,7 +1112,7 @@ class TestLooper:
1103
1112
  _ = self.get_right_nowait()
1104
1113
  self.request_stop_when_empty()
1105
1114
 
1106
- looper = Example(auto_start=True, timeout=SECOND)
1115
+ looper = Example(auto_start=True, timeout=1.0)
1107
1116
  for i in range(25):
1108
1117
  match i % 2 == 0:
1109
1118
  case True:
@@ -1125,7 +1134,7 @@ class TestLooper:
1125
1134
  def test_request_stop_when_empty_already_requested(
1126
1135
  self, *, caplog: LogCaptureFixture
1127
1136
  ) -> None:
1128
- looper = _ExampleLooper()
1137
+ looper = _ExampleCounterLooper()
1129
1138
  for _ in range(2):
1130
1139
  looper.request_stop_when_empty()
1131
1140
  _ = one(
@@ -1138,7 +1147,7 @@ class TestLooper:
1138
1147
  async def test_restart_failure_during_initialization(
1139
1148
  self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
1140
1149
  ) -> None:
1141
- class Example(_ExampleLooper):
1150
+ class Example(_ExampleCounterLooper):
1142
1151
  @override
1143
1152
  async def _initialize_core(self) -> None:
1144
1153
  if self._initialization_attempts == 1:
@@ -1154,7 +1163,7 @@ class TestLooper:
1154
1163
  async def test_restart_failure_during_tear_down(
1155
1164
  self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
1156
1165
  ) -> None:
1157
- class Example(_ExampleLooper):
1166
+ class Example(_ExampleCounterLooper):
1158
1167
  @override
1159
1168
  async def _tear_down_core(self) -> None:
1160
1169
  if self._tear_down_attempts == 1:
@@ -1170,7 +1179,7 @@ class TestLooper:
1170
1179
  async def test_restart_failure_during_tear_down_and_initialization(
1171
1180
  self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
1172
1181
  ) -> None:
1173
- class Example(_ExampleLooper):
1182
+ class Example(_ExampleCounterLooper):
1174
1183
  @override
1175
1184
  async def _initialize_core(self) -> None:
1176
1185
  if self._initialization_attempts == 1:
@@ -1188,11 +1197,19 @@ class TestLooper:
1188
1197
  pattern = rf": encountered _ExampleLooperError\(\) \(tear down\) and then _ExampleLooperError\(\) \(initialization\) whilst restarting{extra}$"
1189
1198
  _ = one(m for m in caplog.messages if search(pattern, m))
1190
1199
 
1200
+ @mark.parametrize("n", [param(0), param(1), param(2)])
1201
+ async def test_run_until_empty(self, *, n: int) -> None:
1202
+ looper = _ExampleQueueLooper(freq=0.05)
1203
+ looper.put_right_nowait(*range(n))
1204
+ async with timeout(1.0), looper:
1205
+ await looper.run_until_empty()
1206
+ assert looper.empty()
1207
+
1191
1208
  @mark.parametrize("auto_start", [param(True), param(False)])
1192
1209
  async def test_sub_looper_one(
1193
1210
  self, *, auto_start: bool, caplog: LogCaptureFixture
1194
1211
  ) -> None:
1195
- looper = _ExampleOuterLooper(auto_start=True, timeout=SECOND)
1212
+ looper = _ExampleOuterLooper(auto_start=True, timeout=1.0)
1196
1213
  looper.inner.auto_start = auto_start
1197
1214
  async with looper:
1198
1215
  ...
@@ -1206,7 +1223,7 @@ class TestLooper:
1206
1223
  restart_successes=13,
1207
1224
  stops=1,
1208
1225
  )
1209
- pattern = r": changing sub-looper _ExampleLooper\(.*?\) to auto-start\.\.\.$"
1226
+ pattern = rf": changing sub-looper {get_class_name(_ExampleCounterLooper)}\(.*?\) to auto-start\.\.\.$"
1210
1227
  matches = [m for m in caplog.messages if bool(search(pattern, m))]
1211
1228
  if auto_start:
1212
1229
  assert len(matches) == 0
@@ -1215,14 +1232,14 @@ class TestLooper:
1215
1232
 
1216
1233
  async def test_sub_loopers_multiple(self) -> None:
1217
1234
  @dataclass(kw_only=True)
1218
- class Example(_ExampleLooper):
1219
- inner1: _ExampleLooper = field(init=False, repr=False)
1220
- inner2: _ExampleLooper = field(init=False, repr=False)
1235
+ class Example(_ExampleCounterLooper):
1236
+ inner1: _ExampleCounterLooper = field(init=False, repr=False)
1237
+ inner2: _ExampleCounterLooper = field(init=False, repr=False)
1221
1238
 
1222
1239
  @override
1223
1240
  def __post_init__(self) -> None:
1224
1241
  super().__post_init__()
1225
- self.inner1 = _ExampleLooper(
1242
+ self.inner1 = _ExampleCounterLooper(
1226
1243
  freq=self.freq / 2,
1227
1244
  backoff=self.backoff / 2,
1228
1245
  logger=self.logger,
@@ -1230,7 +1247,7 @@ class TestLooper:
1230
1247
  timeout_error=self.timeout_error,
1231
1248
  max_count=round(self.max_count / 2),
1232
1249
  )
1233
- self.inner2 = _ExampleLooper(
1250
+ self.inner2 = _ExampleCounterLooper(
1234
1251
  freq=self.freq / 2,
1235
1252
  backoff=self.backoff / 2,
1236
1253
  logger=self.logger,
@@ -1244,7 +1261,7 @@ class TestLooper:
1244
1261
  yield self.inner1
1245
1262
  yield self.inner2
1246
1263
 
1247
- looper = Example(auto_start=True, timeout=SECOND)
1264
+ looper = Example(auto_start=True, timeout=1.0)
1248
1265
  async with looper:
1249
1266
  ...
1250
1267
  self._assert_stats(looper, stops=1)
@@ -1261,7 +1278,7 @@ class TestLooper:
1261
1278
 
1262
1279
  async def test_sub_loopers_nested(self) -> None:
1263
1280
  @dataclass(kw_only=True)
1264
- class Example(_ExampleLooper):
1281
+ class Example(_ExampleCounterLooper):
1265
1282
  middle: _ExampleOuterLooper = field(init=False, repr=False)
1266
1283
 
1267
1284
  @override
@@ -1280,7 +1297,7 @@ class TestLooper:
1280
1297
  def _yield_sub_loopers(self) -> Iterator[Looper]:
1281
1298
  yield self.middle
1282
1299
 
1283
- looper = Example(auto_start=True, timeout=SECOND)
1300
+ looper = Example(auto_start=True, timeout=1.0)
1284
1301
  async with looper:
1285
1302
  ...
1286
1303
  self._assert_stats(looper, stops=1)
@@ -1306,7 +1323,7 @@ class TestLooper:
1306
1323
  async def test_tear_down_already_tearing_down(
1307
1324
  self, *, caplog: LogCaptureFixture
1308
1325
  ) -> None:
1309
- class Example(_ExampleLooper):
1326
+ class Example(_ExampleCounterLooper):
1310
1327
  @override
1311
1328
  async def _tear_down_core(self) -> None:
1312
1329
  if self._tear_down_attempts == 1:
@@ -1321,7 +1338,7 @@ class TestLooper:
1321
1338
  async def test_tear_down_failure(
1322
1339
  self, *, sleep_if_failure: bool, extra: str, caplog: LogCaptureFixture
1323
1340
  ) -> None:
1324
- class Example(_ExampleLooper):
1341
+ class Example(_ExampleCounterLooper):
1325
1342
  @override
1326
1343
  async def _tear_down_core(self) -> None:
1327
1344
  if self._tear_down_attempts == 1:
@@ -1333,14 +1350,21 @@ class TestLooper:
1333
1350
  pattern = rf": encountered _ExampleLooperError\(\) whilst tearing down{extra}$"
1334
1351
  _ = one(m for m in caplog.messages if search(pattern, m))
1335
1352
 
1353
+ async def test_timeout(self) -> None:
1354
+ looper = _ExampleCounterLooper(timeout=1.0)
1355
+ async with looper:
1356
+ with raises(LooperTimeoutError, match="Timeout"):
1357
+ await looper
1358
+ self._assert_stats(looper, stops=1)
1359
+
1336
1360
  def test_with_auto_start(self) -> None:
1337
- looper = _ExampleLooper()
1361
+ looper = _ExampleCounterLooper()
1338
1362
  assert not looper.auto_start
1339
1363
  assert looper.with_auto_start.auto_start
1340
1364
 
1341
1365
  def _assert_stats(
1342
1366
  self,
1343
- looper: _ExampleLooper,
1367
+ looper: _ExampleCounterLooper,
1344
1368
  /,
1345
1369
  *,
1346
1370
  entries: int = 1,
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.125.23"
3
+ __version__ = "0.125.24"
@@ -688,6 +688,7 @@ class Looper(Generic[_T]):
688
688
  auto_start: bool = field(default=False, repr=False)
689
689
  freq: Duration = field(default=SECOND, repr=False)
690
690
  backoff: Duration = field(default=10 * SECOND, repr=False)
691
+ empty_upon_exit: bool = field(default=False, repr=False)
691
692
  logger: str | None = field(default=None, repr=False)
692
693
  timeout: Duration | None = field(default=None, repr=False)
693
694
  timeout_error: type[Exception] = field(default=LooperTimeoutError, repr=False)
@@ -791,6 +792,8 @@ class Looper(Generic[_T]):
791
792
  )
792
793
  _ = await self._stack.__aexit__(exc_type, exc_value, traceback)
793
794
  await self.stop()
795
+ if self.empty_upon_exit:
796
+ await self.run_until_empty()
794
797
  case False:
795
798
  _ = self._debug and self._logger.debug("%s: already exited", self)
796
799
  case _ as never:
@@ -891,6 +894,7 @@ class Looper(Generic[_T]):
891
894
  self,
892
895
  *,
893
896
  auto_start: bool | Sentinel = sentinel,
897
+ empty_upon_exit: bool | Sentinel = sentinel,
894
898
  freq: Duration | Sentinel = sentinel,
895
899
  backoff: Duration | Sentinel = sentinel,
896
900
  logger: str | None | Sentinel = sentinel,
@@ -902,6 +906,7 @@ class Looper(Generic[_T]):
902
906
  return replace_non_sentinel(
903
907
  self,
904
908
  auto_start=auto_start,
909
+ empty_upon_exit=empty_upon_exit,
905
910
  freq=freq,
906
911
  backoff=backoff,
907
912
  logger=logger,
@@ -1061,6 +1066,13 @@ class Looper(Generic[_T]):
1061
1066
  self._core_successes += 1
1062
1067
  await sleep(self._freq)
1063
1068
 
1069
+ async def run_until_empty(self) -> None:
1070
+ """Run until the queue is empty."""
1071
+ while not self.empty():
1072
+ await self.core()
1073
+ if not self.empty():
1074
+ await sleep(self._freq)
1075
+
1064
1076
  @property
1065
1077
  def stats(self) -> _LooperStats:
1066
1078
  """Return the statistics."""