dycw-utilities 0.115.1__tar.gz → 0.116.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/pyproject.toml +2 -2
  3. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_asyncio.py +128 -48
  4. dycw_utilities-0.116.0/src/tests/test_errors.py +28 -0
  5. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_slack_sdk.py +6 -4
  6. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/__init__.py +1 -1
  7. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/asyncio.py +66 -21
  8. dycw_utilities-0.116.0/src/utilities/errors.py +34 -0
  9. dycw_utilities-0.115.1/src/tests/test_errors.py +0 -12
  10. dycw_utilities-0.115.1/src/utilities/errors.py +0 -17
  11. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/.gitignore +0 -0
  12. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/LICENSE +0 -0
  13. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/README.md +0 -0
  14. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/__init__.py +0 -0
  15. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/conftest.py +0 -0
  16. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/__init__.py +0 -0
  17. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_missing/__init__.py +0 -0
  18. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_missing/module.py +0 -0
  19. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/__init__.py +0 -0
  20. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/outer_1.py +0 -0
  21. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/outer_2.py +0 -0
  22. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  23. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  24. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  25. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  26. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_without/__init__.py +0 -0
  27. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_without/module_1.py +0 -0
  28. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/package_without/module_2.py +0 -0
  29. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/standalone.py +0 -0
  30. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/modules/with_imports.py +0 -0
  31. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  32. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  33. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  34. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  35. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  36. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  37. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  38. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  39. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/__init__.py +0 -0
  40. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/test_async_service/__init__.py +0 -0
  41. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/test_async_service/__main__.py +0 -0
  42. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/test_async_service/run.sh +0 -0
  43. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  44. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  45. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  46. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_altair.py +0 -0
  47. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_astor.py +0 -0
  48. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_atomicwrites.py +0 -0
  49. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_atools.py +0 -0
  50. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_cachetools.py +0 -0
  51. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_click.py +0 -0
  52. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_concurrent.py +0 -0
  53. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_contextlib.py +0 -0
  54. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_contextvars.py +0 -0
  55. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_cryptography.py +0 -0
  56. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_cvxpy.py +0 -0
  57. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_dataclasses.py +0 -0
  58. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_datetime.py +0 -0
  59. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_enum.py +0 -0
  60. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_eventkit.py +0 -0
  61. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_fastapi.py +0 -0
  62. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_fpdf2.py +0 -0
  63. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_functions.py +0 -0
  64. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_functools.py +0 -0
  65. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_getpass.py +0 -0
  66. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_git.py +0 -0
  67. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_hashlib.py +0 -0
  68. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_http.py +0 -0
  69. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_hypothesis.py +0 -0
  70. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_ipython.py +0 -0
  71. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_iterables.py +0 -0
  72. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_jupyter.py +0 -0
  73. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_lightweight_charts.py +0 -0
  74. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_logging.py +0 -0
  75. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_loguru.py +0 -0
  76. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_luigi.py +0 -0
  77. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_math.py +0 -0
  78. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_memory_profiler.py +0 -0
  79. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_modules.py +0 -0
  80. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_more_itertools.py +0 -0
  81. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_numpy.py +0 -0
  82. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_operator.py +0 -0
  83. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_optuna.py +0 -0
  84. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_orjson.py +0 -0
  85. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_os.py +0 -0
  86. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_parse.py +0 -0
  87. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pathlib.py +0 -0
  88. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_period.py +0 -0
  89. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pickle.py +0 -0
  90. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_platform.py +0 -0
  91. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_polars.py +0 -0
  92. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_polars_ols.py +0 -0
  93. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pqdm.py +0 -0
  94. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pydantic.py +0 -0
  95. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pyinstrument.py +0 -0
  96. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pyrsistent.py +0 -0
  97. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pytest.py +0 -0
  98. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_pytest_regressions.py +0 -0
  99. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_python_dotenv.py +0 -0
  100. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_random.py +0 -0
  101. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_re.py +0 -0
  102. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_redis.py +0 -0
  103. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_reprlib.py +0 -0
  104. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_rich.py +0 -0
  105. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_scipy.py +0 -0
  106. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_sentinel.py +0 -0
  107. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_shelve.py +0 -0
  108. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_socket.py +0 -0
  109. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_sqlalchemy.py +0 -0
  110. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  111. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_statsmodel.py +0 -0
  112. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_streamlit.py +0 -0
  113. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_sys.py +0 -0
  114. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_tempfile.py +0 -0
  115. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_tenacity.py +0 -0
  116. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_text.py +0 -0
  117. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_threading.py +0 -0
  118. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_timer.py +0 -0
  119. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback.py +0 -0
  120. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
  121. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/chain.py +0 -0
  122. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  123. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  124. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  125. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/many.py +0 -0
  126. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/one.py +0 -0
  127. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
  128. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  129. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  130. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/two.py +0 -0
  131. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
  132. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_types.py +0 -0
  133. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_typing.py +0 -0
  134. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  135. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  136. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  137. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_tzdata.py +0 -0
  138. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_tzlocal.py +0 -0
  139. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_uuid.py +0 -0
  140. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_version.py +0 -0
  141. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_warnings.py +0 -0
  142. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_whenever.py +0 -0
  143. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_zipfile.py +0 -0
  144. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/tests/test_zoneinfo.py +0 -0
  145. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/altair.py +0 -0
  146. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/astor.py +0 -0
  147. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/atomicwrites.py +0 -0
  148. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/atools.py +0 -0
  149. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/cachetools.py +0 -0
  150. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/click.py +0 -0
  151. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/concurrent.py +0 -0
  152. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/contextlib.py +0 -0
  153. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/contextvars.py +0 -0
  154. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/cryptography.py +0 -0
  155. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/cvxpy.py +0 -0
  156. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/dataclasses.py +0 -0
  157. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/datetime.py +0 -0
  158. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/enum.py +0 -0
  159. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/eventkit.py +0 -0
  160. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/fastapi.py +0 -0
  161. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/fpdf2.py +0 -0
  162. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/functions.py +0 -0
  163. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/functools.py +0 -0
  164. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/getpass.py +0 -0
  165. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/git.py +0 -0
  166. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/hashlib.py +0 -0
  167. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/http.py +0 -0
  168. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/hypothesis.py +0 -0
  169. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/ipython.py +0 -0
  170. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/iterables.py +0 -0
  171. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/jupyter.py +0 -0
  172. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/lightweight_charts.py +0 -0
  173. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/logging.py +0 -0
  174. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/loguru.py +0 -0
  175. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/luigi.py +0 -0
  176. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/math.py +0 -0
  177. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/memory_profiler.py +0 -0
  178. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/modules.py +0 -0
  179. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/more_itertools.py +0 -0
  180. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/numpy.py +0 -0
  181. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/operator.py +0 -0
  182. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/optuna.py +0 -0
  183. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/orjson.py +0 -0
  184. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/os.py +0 -0
  185. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/parse.py +0 -0
  186. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pathlib.py +0 -0
  187. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/period.py +0 -0
  188. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pickle.py +0 -0
  189. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/platform.py +0 -0
  190. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/polars.py +0 -0
  191. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/polars_ols.py +0 -0
  192. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pqdm.py +0 -0
  193. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/py.typed +0 -0
  194. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pydantic.py +0 -0
  195. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pyinstrument.py +0 -0
  196. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pyrsistent.py +0 -0
  197. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pytest.py +0 -0
  198. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/pytest_regressions.py +0 -0
  199. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/python_dotenv.py +0 -0
  200. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/random.py +0 -0
  201. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/re.py +0 -0
  202. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/redis.py +0 -0
  203. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/reprlib.py +0 -0
  204. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/rich.py +0 -0
  205. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/scipy.py +0 -0
  206. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/sentinel.py +0 -0
  207. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/shelve.py +0 -0
  208. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/slack_sdk.py +0 -0
  209. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/socket.py +0 -0
  210. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/sqlalchemy.py +0 -0
  211. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/sqlalchemy_polars.py +0 -0
  212. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/statsmodels.py +0 -0
  213. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/streamlit.py +0 -0
  214. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/sys.py +0 -0
  215. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/tempfile.py +0 -0
  216. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/tenacity.py +0 -0
  217. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/text.py +0 -0
  218. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/threading.py +0 -0
  219. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/timer.py +0 -0
  220. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/traceback.py +0 -0
  221. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/types.py +0 -0
  222. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/typing.py +0 -0
  223. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/tzdata.py +0 -0
  224. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/tzlocal.py +0 -0
  225. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/uuid.py +0 -0
  226. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/version.py +0 -0
  227. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/warnings.py +0 -0
  228. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/whenever.py +0 -0
  229. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/zipfile.py +0 -0
  230. {dycw_utilities-0.115.1 → dycw_utilities-0.116.0}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.115.1
3
+ Version: 0.116.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -92,7 +92,7 @@ dependencies = [
92
92
  name = "dycw-utilities"
93
93
  readme = "README.md"
94
94
  requires-python = ">= 3.12"
95
- version = "0.115.1"
95
+ version = "0.116.0"
96
96
 
97
97
  [project.optional-dependencies]
98
98
  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.115.1"
337
+ current_version = "0.116.0"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -12,6 +12,7 @@ from asyncio import (
12
12
  )
13
13
  from collections import Counter
14
14
  from dataclasses import dataclass, field
15
+ from functools import partial
15
16
  from itertools import chain, count
16
17
  from re import search
17
18
  from typing import TYPE_CHECKING, Self, override
@@ -37,6 +38,7 @@ from utilities.asyncio import (
37
38
  InfiniteLooper,
38
39
  InfiniteLooperError,
39
40
  InfiniteQueueLooper,
41
+ InfiniteQueueLooperError,
40
42
  QueueProcessor,
41
43
  UniquePriorityQueue,
42
44
  UniqueQueue,
@@ -58,7 +60,7 @@ from utilities.sentinel import Sentinel, sentinel
58
60
  from utilities.timer import Timer
59
61
 
60
62
  if TYPE_CHECKING:
61
- from collections.abc import Iterator
63
+ from collections.abc import Callable, Iterator
62
64
 
63
65
  from utilities.types import Coroutine1, Duration, MaybeCallableEvent, MaybeType
64
66
 
@@ -274,12 +276,12 @@ class TestEnhancedTaskGroup:
274
276
 
275
277
  async def test_timeout_pass(self) -> None:
276
278
  async with EnhancedTaskGroup(timeout=0.2) as tg:
277
- _ = tg.create_task(sleep_dur(duration=0.1))
279
+ _ = tg.create_task(sleep(0.1))
278
280
 
279
281
  async def test_timeout_fail(self) -> None:
280
282
  with raises(ExceptionGroup) as exc_info:
281
283
  async with EnhancedTaskGroup(timeout=0.05) as tg:
282
- _ = tg.create_task(sleep_dur(duration=0.1))
284
+ _ = tg.create_task(sleep(0.1))
283
285
  assert len(exc_info.value.exceptions) == 1
284
286
  error = one(exc_info.value.exceptions)
285
287
  assert isinstance(error, TimeoutError)
@@ -289,7 +291,7 @@ class TestEnhancedTaskGroup:
289
291
 
290
292
  with raises(ExceptionGroup) as exc_info:
291
293
  async with EnhancedTaskGroup(timeout=0.05, error=CustomError) as tg:
292
- _ = tg.create_task(sleep_dur(duration=0.1))
294
+ _ = tg.create_task(sleep(0.1))
293
295
  assert len(exc_info.value.exceptions) == 1
294
296
  error = one(exc_info.value.exceptions)
295
297
  assert isinstance(error, CustomError)
@@ -376,19 +378,50 @@ class TestInfiniteLooper:
376
378
  _ = await looper()
377
379
 
378
380
  async def test_hashable(self) -> None:
379
- class CustomError(BaseException): ...
381
+ class CustomError(Exception): ...
380
382
 
381
383
  @dataclass(kw_only=True, unsafe_hash=True)
382
384
  class Example(InfiniteLooper[None]):
385
+ @override
386
+ def _yield_events_and_exceptions(
387
+ self,
388
+ ) -> Iterator[tuple[None, MaybeType[Exception]]]:
389
+ yield (None, CustomError)
390
+
391
+ looper = Example(sleep_core=0.1)
392
+ _ = hash(looper)
393
+
394
+ async def test_with_coroutine_self_set_event(self) -> None:
395
+ external: int = 0
396
+
397
+ async def inc_external(obj: Example, /) -> None:
398
+ nonlocal external
399
+ while True:
400
+ external += 1
401
+ obj.counter += 1
402
+ await sleep(0.05)
403
+
404
+ class CustomError(Exception): ...
405
+
406
+ @dataclass(kw_only=True)
407
+ class Example(InfiniteLooper[None]):
408
+ initializations: int = 0
383
409
  counter: int = 0
384
410
 
385
411
  @override
386
412
  async def _initialize(self) -> None:
413
+ self.initializations += 1
387
414
  self.counter = 0
388
415
 
389
416
  @override
390
417
  async def _core(self) -> None:
391
418
  self.counter += 1
419
+ if self.counter >= 5:
420
+ self._set_event(None)
421
+
422
+ @override
423
+ def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
424
+ yield partial(inc_external, self)
392
425
 
393
426
  @override
394
427
  def _yield_events_and_exceptions(
@@ -396,68 +429,95 @@ class TestInfiniteLooper:
396
429
  ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
397
430
  yield (None, CustomError)
398
431
 
399
- looper = Example(sleep_core=0.1)
400
- _ = hash(looper)
432
+ looper = Example(sleep_core=0.05, sleep_restart=0.05)
433
+ with raises(TimeoutError):
434
+ async with timeout_dur(duration=1.0):
435
+ await looper()
436
+ assert 4 <= looper.initializations <= 6
437
+ assert 0 <= looper.counter <= 7
438
+ assert 17 <= external <= 21
401
439
 
402
- async def test_with_coroutines(self) -> None:
403
- class ChildError(BaseException): ...
440
+ async def test_with_coroutine_self_error(self) -> None:
441
+ class CustomError(Exception): ...
442
+
443
+ async def dummy() -> None:
444
+ _ = await Event().wait()
404
445
 
405
446
  @dataclass(kw_only=True)
406
- class Child(InfiniteLooper[None]):
447
+ class Example(InfiniteLooper[None]):
448
+ initializations: int = 0
407
449
  counter: int = 0
408
450
 
409
451
  @override
410
452
  async def _initialize(self) -> None:
453
+ self.initializations += 1
411
454
  self.counter = 0
412
455
 
413
456
  @override
414
457
  async def _core(self) -> None:
415
458
  self.counter += 1
416
- if self.counter >= 10:
417
- self._set_event(None)
459
+ if self.counter >= 5:
460
+ raise CustomError
461
+
462
+ @override
463
+ def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
464
+ yield dummy
418
465
 
419
466
  @override
420
467
  def _yield_events_and_exceptions(
421
468
  self,
422
469
  ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
423
- yield (None, ChildError)
470
+ yield (None, CustomError)
471
+
472
+ looper = Example(sleep_core=0.05, sleep_restart=0.05)
473
+ with raises(TimeoutError):
474
+ async with timeout_dur(duration=1.0):
475
+ await looper()
476
+ assert 3 <= looper.initializations <= 5
477
+ assert 0 <= looper.counter <= 5
424
478
 
425
- class ParentError(BaseException): ...
479
+ @given(logger=just("logger") | none())
480
+ async def test_with_coroutine_other_coroutine_error(
481
+ self, *, logger: str | None
482
+ ) -> None:
483
+ class CustomError(Exception): ...
484
+
485
+ async def dummy() -> None:
486
+ for i in count():
487
+ if i >= 5:
488
+ raise CustomError
489
+ await sleep(0.05)
426
490
 
427
491
  @dataclass(kw_only=True)
428
- class Parent(InfiniteLooper[None]):
492
+ class Example(InfiniteLooper[None]):
493
+ initializations: int = 0
429
494
  counter: int = 0
430
- child: Child
431
495
 
432
496
  @override
433
497
  async def _initialize(self) -> None:
498
+ self.initializations += 1
434
499
  self.counter = 0
435
500
 
436
501
  @override
437
502
  async def _core(self) -> None:
438
503
  self.counter += 1
439
- self.child.counter += 1
440
- if self.counter >= 10:
441
- self._set_event(None)
442
504
 
443
505
  @override
444
- def _yield_coroutines(self) -> Iterator[Coroutine1[None]]:
445
- yield self.child()
506
+ def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
507
+ yield dummy
446
508
 
447
509
  @override
448
510
  def _yield_events_and_exceptions(
449
511
  self,
450
512
  ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
451
- yield (None, ParentError)
513
+ yield (None, CustomError)
452
514
 
453
- parent = Parent(sleep_core=0.1, child=Child(sleep_core=0.1))
454
- with raises(BaseExceptionGroup) as error:
455
- async with timeout_dur(duration=1.5):
456
- await parent()
457
- inner = one(error.value.exceptions)
458
- assert isinstance(inner, ChildError)
459
- assert 10 <= parent.child.counter <= 15
460
- assert 3 <= parent.counter <= 7
515
+ looper = Example(sleep_core=0.05, sleep_restart=0.05, logger=logger)
516
+ with raises(CancelledError):
517
+ async with timeout_dur(duration=1.0):
518
+ await looper()
519
+ assert 3 <= looper.initializations <= 5
520
+ assert 1 <= looper.counter <= 5
461
521
 
462
522
  @given(logger=just("logger") | none())
463
523
  async def test_error_upon_initialize(self, *, logger: str | None) -> None:
@@ -473,12 +533,13 @@ class TestInfiniteLooper:
473
533
  async def _core(self) -> None:
474
534
  raise NotImplementedError
475
535
 
536
+ looper = Example(sleep_core=0.1, logger=logger)
476
537
  with raises(TimeoutError):
477
538
  async with timeout_dur(duration=0.5):
478
- _ = await Example(sleep_core=0.1, logger=logger)()
539
+ _ = await looper()
479
540
 
480
541
  @given(logger=just("logger") | none())
481
- async def test_error_upon_core(self, *, logger: str | None) -> None:
542
+ async def test_error_group_upon_coroutines(self, *, logger: str | None) -> None:
482
543
  class CustomError(Exception): ...
483
544
 
484
545
  @dataclass(kw_only=True)
@@ -493,9 +554,10 @@ class TestInfiniteLooper:
493
554
  ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
494
555
  yield (None, CustomError)
495
556
 
557
+ looper = Example(sleep_core=0.1, logger=logger)
496
558
  with raises(TimeoutError):
497
559
  async with timeout_dur(duration=0.5):
498
- _ = await Example(sleep_core=0.1, logger=logger)()
560
+ _ = await looper()
499
561
 
500
562
  async def test_error_no_event_found(self) -> None:
501
563
  @dataclass(kw_only=True)
@@ -513,7 +575,7 @@ class TestInfiniteLooper:
513
575
  self._set_event(None)
514
576
 
515
577
  looper = Example(sleep_core=0.1)
516
- with raises(InfiniteLooperError, match="No event None found"):
578
+ with raises(InfiniteLooperError, match="'Example' does not have an event None"):
517
579
  _ = await looper()
518
580
 
519
581
 
@@ -527,18 +589,18 @@ class TestInfiniteQueueLooper:
527
589
  async def _process_items(self, *items: int) -> None:
528
590
  self.output.update(items)
529
591
 
530
- processor = Example(sleep_core=0.1)
592
+ looper = Example(sleep_core=0.05)
531
593
 
532
594
  async def add_items() -> None:
533
595
  for i in count():
534
- processor.put_items_nowait(i)
535
- await sleep(0.1)
596
+ looper.put_items_nowait(i)
597
+ await sleep(0.05)
536
598
 
537
599
  with raises(ExceptionGroup): # noqa: PT012
538
600
  async with EnhancedTaskGroup(timeout=1.0) as tg:
539
- _ = tg.create_task(processor())
601
+ _ = tg.create_task(looper())
540
602
  _ = tg.create_task(add_items())
541
- assert 5 <= len(processor.output) <= 15
603
+ assert 15 <= len(looper.output) <= 20
542
604
 
543
605
  async def test_no_items(self) -> None:
544
606
  @dataclass(kw_only=True)
@@ -549,12 +611,13 @@ class TestInfiniteQueueLooper:
549
611
  async def _process_items(self, *items: int) -> None:
550
612
  self.output.update(items)
551
613
 
552
- processor = Example(sleep_core=0.1)
614
+ looper = Example(sleep_core=0.05)
553
615
  with raises(TimeoutError):
554
616
  async with timeout_dur(duration=0.5):
555
- _ = await processor()
617
+ _ = await looper()
556
618
 
557
- async def test_error_process_items(self) -> None:
619
+ @given(logger=just("logger") | none())
620
+ async def test_error_process_items(self, *, logger: str | None) -> None:
558
621
  class CustomError(Exception): ...
559
622
 
560
623
  @dataclass(kw_only=True)
@@ -565,11 +628,28 @@ class TestInfiniteQueueLooper:
565
628
  async def _process_items(self, *items: int) -> None:
566
629
  raise CustomError(*items)
567
630
 
568
- processor = Example(sleep_core=0.1)
569
- processor.put_items_nowait(1)
631
+ looper = Example(sleep_core=0.05, logger=logger)
632
+ looper.put_items_nowait(1)
570
633
  with raises(TimeoutError):
571
634
  async with timeout_dur(duration=0.5):
572
- _ = await processor()
635
+ _ = await looper()
636
+
637
+ async def test_error_infinite_queue_looper(self) -> None:
638
+ class CustomError(Exception): ...
639
+
640
+ @dataclass(kw_only=True)
641
+ class Example(InfiniteQueueLooper[None, int]):
642
+ @override
643
+ async def _process_items(self, *items: int) -> None:
644
+ raise CustomError(*items)
645
+
646
+ looper = Example(sleep_core=0.1)
647
+ looper.put_items_nowait(1)
648
+ with raises(
649
+ InfiniteQueueLooperError,
650
+ match=r"'Example' encountered CustomError\(1\) whilst processing 1 item\(s\): \[1\]",
651
+ ):
652
+ _ = await looper._core()
573
653
 
574
654
 
575
655
  class TestPutAndGetItems:
@@ -852,19 +932,19 @@ class TestStreamCommand:
852
932
  class TestTimeoutDur:
853
933
  async def test_pass(self) -> None:
854
934
  async with timeout_dur(duration=0.2):
855
- await sleep_dur(duration=0.1)
935
+ await sleep(0.1)
856
936
 
857
937
  async def test_fail(self) -> None:
858
938
  with raises(TimeoutError):
859
939
  async with timeout_dur(duration=0.05):
860
- await sleep_dur(duration=0.1)
940
+ await sleep(0.1)
861
941
 
862
942
  async def test_custom_error(self) -> None:
863
943
  class CustomError(Exception): ...
864
944
 
865
945
  with raises(CustomError):
866
946
  async with timeout_dur(duration=0.05, error=CustomError):
867
- await sleep_dur(duration=0.1)
947
+ await sleep(0.1)
868
948
 
869
949
 
870
950
  if __name__ == "__main__":
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from pytest import raises
4
+
5
+ from utilities.errors import ImpossibleCaseError, repr_error
6
+
7
+
8
+ class TestImpossibleCaseError:
9
+ def test_main(self) -> None:
10
+ x = None
11
+ with raises(ImpossibleCaseError, match=r"Case must be possible: x=None\."):
12
+ raise ImpossibleCaseError(case=[f"{x=}"])
13
+
14
+
15
+ class TestReprError:
16
+ def test_class(self) -> None:
17
+ class CustomError(Exception): ...
18
+
19
+ result = repr_error(CustomError)
20
+ expected = "CustomError"
21
+ assert result == expected
22
+
23
+ def test_instance(self) -> None:
24
+ class CustomError(Exception): ...
25
+
26
+ result = repr_error(CustomError())
27
+ expected = "CustomError()"
28
+ assert result == expected
@@ -155,10 +155,12 @@ class TestSlackHandlerIQL:
155
155
  messages.append(text)
156
156
 
157
157
  logger = getLogger(str(tmp_path))
158
- logger.addHandler(handler := SlackHandlerIQL("url", sender=sender))
158
+ logger.addHandler(
159
+ handler := SlackHandlerIQL("url", sleep_core=0.05, sender=sender)
160
+ )
159
161
 
160
162
  async def sleep_then_log() -> None:
161
- await sleep_dur(duration=0.1)
163
+ await sleep_dur(duration=0.05)
162
164
  logger.warning("message")
163
165
 
164
166
  with raises(ExceptionGroup): # noqa: PT012
@@ -173,10 +175,10 @@ class TestSlackHandlerIQL:
173
175
  async def test_real(self, *, tmp_path: Path) -> None:
174
176
  url = get_env_var("SLACK")
175
177
  logger = getLogger(str(tmp_path))
176
- logger.addHandler(handler := SlackHandlerIQL(url))
178
+ logger.addHandler(handler := SlackHandlerIQL(url, sleep_core=0.05))
177
179
 
178
180
  async def sleep_then_log() -> None:
179
- await sleep_dur(duration=0.1)
181
+ await sleep_dur(duration=0.05)
180
182
  for i in range(10):
181
183
  logger.warning(
182
184
  "message %d from %s", i, TestSlackHandlerIQL.test_real.__qualname__
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.115.1"
3
+ __version__ = "0.116.0"
@@ -30,6 +30,7 @@ from subprocess import PIPE
30
30
  from sys import stderr, stdout
31
31
  from typing import (
32
32
  TYPE_CHECKING,
33
+ Any,
33
34
  Generic,
34
35
  NoReturn,
35
36
  Self,
@@ -41,8 +42,9 @@ from typing import (
41
42
  )
42
43
 
43
44
  from utilities.datetime import MILLISECOND, MINUTE, SECOND, datetime_duration_to_float
44
- from utilities.errors import ImpossibleCaseError
45
+ from utilities.errors import ImpossibleCaseError, repr_error
45
46
  from utilities.functions import ensure_int, ensure_not_none, get_class_name
47
+ from utilities.reprlib import get_repr
46
48
  from utilities.sentinel import Sentinel, sentinel
47
49
  from utilities.types import (
48
50
  Coroutine1,
@@ -379,17 +381,19 @@ class InfiniteLooper(ABC, Generic[THashable]):
379
381
  self._error_upon_core(error)
380
382
  await sleep_dur(duration=self.sleep_restart)
381
383
 
382
- async def _run_looper_with_coroutines(self, *coroutines: Coroutine1) -> None:
384
+ async def _run_looper_with_coroutines(
385
+ self, *coroutines: Callable[[], Coroutine1[None]]
386
+ ) -> None:
383
387
  """Run multiple loopers."""
384
388
  while True:
385
389
  self._reset_events()
386
390
  try:
387
391
  async with TaskGroup() as tg:
388
392
  _ = tg.create_task(self._run_looper())
389
- _ = list(map(tg.create_task, coroutines))
390
- except Exception as error: # noqa: BLE001
391
- self._error_upon_core(error) # pragma: no cover
392
- await sleep_dur(duration=self.sleep_restart) # pragma: no cover
393
+ _ = [tg.create_task(c()) for c in coroutines]
394
+ except ExceptionGroup as error:
395
+ self._error_group_upon_coroutines(error)
396
+ await sleep_dur(duration=self.sleep_restart)
393
397
 
394
398
  async def _initialize(self) -> None:
395
399
  """Initialize the loop."""
@@ -401,9 +405,9 @@ class InfiniteLooper(ABC, Generic[THashable]):
401
405
  """Handle any errors upon initializing the looper."""
402
406
  if self.logger is not None:
403
407
  getLogger(name=self.logger).error(
404
- "Error initializing %r due to %r; sleeping for %s...",
408
+ "%r encountered %r whilst initializing; sleeping for %s...",
405
409
  get_class_name(self),
406
- repr(error),
410
+ repr_error(error),
407
411
  self.sleep_restart,
408
412
  )
409
413
 
@@ -411,12 +415,25 @@ class InfiniteLooper(ABC, Generic[THashable]):
411
415
  """Handle any errors upon running the core function."""
412
416
  if self.logger is not None:
413
417
  getLogger(name=self.logger).error(
414
- "Error running %r due to %r; sleeping for %s...",
418
+ "%r encountered %r; sleeping for %s...",
415
419
  get_class_name(self),
416
- repr(error),
420
+ repr_error(error),
417
421
  self.sleep_restart,
418
422
  )
419
423
 
424
+ def _error_group_upon_coroutines(self, group: ExceptionGroup, /) -> None:
425
+ """Handle any errors upon running the core function."""
426
+ if self.logger is not None:
427
+ errors = group.exceptions
428
+ n = len(errors)
429
+ msgs = [f"{get_class_name(self)!r} encountered {n} error(s):"]
430
+ msgs.extend(
431
+ f"- Error #{i}/{n}: {repr_error(e)}"
432
+ for i, e in enumerate(errors, start=1)
433
+ )
434
+ msgs.append(f"Sleeping for {self.sleep_restart}...")
435
+ getLogger(name=self.logger).error("\n".join(msgs))
436
+
420
437
  def _raise_error(self, event: THashable, /) -> NoReturn:
421
438
  """Raise the error corresponding to given event."""
422
439
  mapping = dict(self._yield_events_and_exceptions())
@@ -434,10 +451,10 @@ class InfiniteLooper(ABC, Generic[THashable]):
434
451
  try:
435
452
  event_obj = self._events[event]
436
453
  except KeyError:
437
- raise InfiniteLooperError(event=event) from None
454
+ raise InfiniteLooperError(looper=self, event=event) from None
438
455
  event_obj.set()
439
456
 
440
- def _yield_coroutines(self) -> Iterator[Coroutine1[None]]:
457
+ def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
441
458
  """Yield any other coroutines which must also be run."""
442
459
  yield from []
443
460
 
@@ -450,11 +467,12 @@ class InfiniteLooper(ABC, Generic[THashable]):
450
467
 
451
468
  @dataclass(kw_only=True, slots=True)
452
469
  class InfiniteLooperError(Exception):
470
+ looper: InfiniteLooper[Any]
453
471
  event: Hashable
454
472
 
455
473
  @override
456
474
  def __str__(self) -> str:
457
- return f"No event {self.event!r} found"
475
+ return f"{get_class_name(self.looper)!r} does not have an event {self.event!r}"
458
476
 
459
477
 
460
478
  ##
@@ -466,25 +484,24 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
466
484
 
467
485
  queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
468
486
  _queue: Queue[_T] = field(init=False)
469
- _current: Queue[_T] = field(init=False)
470
487
 
471
488
  @override
472
489
  def __post_init__(self) -> None:
473
490
  super().__post_init__()
474
491
  self._queue = self.queue_type()
475
- self._current = self.queue_type()
476
492
 
477
493
  @override
478
494
  async def _core(self) -> None:
479
495
  """Run the core part of the loop."""
480
- items = await get_items(self._queue)
481
- _ = get_items_nowait(self._current)
482
- put_items_nowait(items, self._current)
496
+ items = get_items_nowait(self._queue)
497
+ if len(items) == 0:
498
+ return
483
499
  try:
484
500
  await self._process_items(*items)
485
- except Exception:
486
- put_items_nowait(items, self._queue)
487
- raise
501
+ except Exception as error: # noqa: BLE001
502
+ raise InfiniteQueueLooperError(
503
+ looper=self, items=items, error=error
504
+ ) from None
488
505
 
489
506
  @abstractmethod
490
507
  async def _process_items(self, *items: _T) -> None:
@@ -494,6 +511,33 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
494
511
  """Put items into the queue."""
495
512
  put_items_nowait(items, self._queue)
496
513
 
514
+ @override
515
+ def _error_upon_core(self, error: Exception, /) -> None:
516
+ """Handle any errors upon running the core function."""
517
+ if self.logger is not None:
518
+ if isinstance(error, InfiniteQueueLooperError):
519
+ getLogger(name=self.logger).error(
520
+ "%r encountered %s whilst processing %d item(s) %s; sleeping for %s...",
521
+ get_class_name(self),
522
+ repr_error(error.error),
523
+ len(error.items),
524
+ get_repr(error.items),
525
+ self.sleep_restart,
526
+ )
527
+ else:
528
+ super()._error_upon_core(error) # pragma: no cover
529
+
530
+
531
+ @dataclass(kw_only=True, slots=True)
532
+ class InfiniteQueueLooperError(Exception, Generic[_T]):
533
+ looper: InfiniteQueueLooper[Any, Any]
534
+ items: Sequence[_T]
535
+ error: Exception
536
+
537
+ @override
538
+ def __str__(self) -> str:
539
+ return f"{get_class_name(self.looper)!r} encountered {repr_error(self.error)} whilst processing {len(self.items)} item(s): {get_repr(self.items)}"
540
+
497
541
 
498
542
  ##
499
543
 
@@ -703,6 +747,7 @@ __all__ = [
703
747
  "InfiniteLooper",
704
748
  "InfiniteLooperError",
705
749
  "InfiniteQueueLooper",
750
+ "InfiniteQueueLooperError",
706
751
  "QueueProcessor",
707
752
  "StreamCommandOutput",
708
753
  "UniquePriorityQueue",
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, assert_never, override
5
+
6
+ if TYPE_CHECKING:
7
+ from utilities.types import MaybeType
8
+
9
+
10
+ @dataclass(kw_only=True, slots=True)
11
+ class ImpossibleCaseError(Exception):
12
+ case: list[str]
13
+
14
+ @override
15
+ def __str__(self) -> str:
16
+ desc = ", ".join(self.case)
17
+ return f"Case must be possible: {desc}."
18
+
19
+
20
+ ##
21
+
22
+
23
+ def repr_error(error: MaybeType[Exception], /) -> str:
24
+ """Get a string representation of an error."""
25
+ match error:
26
+ case Exception() as error_obj:
27
+ return f"{error_obj.__class__.__name__}({error_obj})"
28
+ case type() as error_cls:
29
+ return error_cls.__name__
30
+ case _ as never:
31
+ assert_never(never)
32
+
33
+
34
+ __all__ = ["ImpossibleCaseError", "repr_error"]
@@ -1,12 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pytest import raises
4
-
5
- from utilities.errors import ImpossibleCaseError
6
-
7
-
8
- class TestImpossibleCaseError:
9
- def test_main(self) -> None:
10
- x = None
11
- with raises(ImpossibleCaseError, match=r"Case must be possible: x=None\."):
12
- raise ImpossibleCaseError(case=[f"{x=}"])