dycw-utilities 0.121.1__tar.gz → 0.122.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 (223) hide show
  1. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/pyproject.toml +2 -2
  3. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_asyncio.py +316 -142
  4. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_fastapi.py +4 -11
  5. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_redis.py +13 -13
  6. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_slack_sdk.py +3 -20
  7. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_sqlalchemy.py +1 -9
  8. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/__init__.py +1 -1
  9. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/asyncio.py +132 -29
  10. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/fastapi.py +4 -0
  11. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/redis.py +1 -1
  12. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/sqlalchemy.py +1 -1
  13. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/.gitignore +0 -0
  14. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/LICENSE +0 -0
  15. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/README.md +0 -0
  16. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/__init__.py +0 -0
  17. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/conftest.py +0 -0
  18. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/__init__.py +0 -0
  19. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_missing/__init__.py +0 -0
  20. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_missing/module.py +0 -0
  21. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/__init__.py +0 -0
  22. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/outer_1.py +0 -0
  23. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/outer_2.py +0 -0
  24. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  25. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  26. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  27. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  28. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_without/__init__.py +0 -0
  29. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_without/module_1.py +0 -0
  30. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/package_without/module_2.py +0 -0
  31. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/standalone.py +0 -0
  32. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/modules/with_imports.py +0 -0
  33. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  34. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  35. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  36. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  37. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  38. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  39. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  40. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  41. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_altair.py +0 -0
  42. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_astor.py +0 -0
  43. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_atomicwrites.py +0 -0
  44. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_atools.py +0 -0
  45. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_cachetools.py +0 -0
  46. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_click.py +0 -0
  47. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_concurrent.py +0 -0
  48. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_contextlib.py +0 -0
  49. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_contextvars.py +0 -0
  50. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_cryptography.py +0 -0
  51. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_cvxpy.py +0 -0
  52. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_dataclasses.py +0 -0
  53. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_datetime.py +0 -0
  54. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_enum.py +0 -0
  55. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_errors.py +0 -0
  56. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_eventkit.py +0 -0
  57. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_fpdf2.py +0 -0
  58. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_functions.py +0 -0
  59. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_functools.py +0 -0
  60. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_getpass.py +0 -0
  61. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_git.py +0 -0
  62. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_hashlib.py +0 -0
  63. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_http.py +0 -0
  64. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_hypothesis.py +0 -0
  65. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_importlib.py +0 -0
  66. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_ipython.py +0 -0
  67. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_iterables.py +0 -0
  68. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_jupyter.py +0 -0
  69. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_lightweight_charts.py +0 -0
  70. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_logging.py +0 -0
  71. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_loguru.py +0 -0
  72. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_luigi.py +0 -0
  73. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_math.py +0 -0
  74. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_memory_profiler.py +0 -0
  75. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_modules.py +0 -0
  76. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_more_itertools.py +0 -0
  77. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_numpy.py +0 -0
  78. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_operator.py +0 -0
  79. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_optuna.py +0 -0
  80. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_orjson.py +0 -0
  81. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_os.py +0 -0
  82. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_parse.py +0 -0
  83. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pathlib.py +0 -0
  84. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_period.py +0 -0
  85. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pickle.py +0 -0
  86. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_platform.py +0 -0
  87. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_polars.py +0 -0
  88. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_polars_ols.py +0 -0
  89. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pqdm.py +0 -0
  90. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pydantic.py +0 -0
  91. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pyinstrument.py +0 -0
  92. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pyrsistent.py +0 -0
  93. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pytest.py +0 -0
  94. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_pytest_regressions.py +0 -0
  95. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_python_dotenv.py +0 -0
  96. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_random.py +0 -0
  97. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_re.py +0 -0
  98. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_reprlib.py +0 -0
  99. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_rich.py +0 -0
  100. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_scipy.py +0 -0
  101. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_sentinel.py +0 -0
  102. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_shelve.py +0 -0
  103. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_socket.py +0 -0
  104. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  105. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_statsmodel.py +0 -0
  106. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_streamlit.py +0 -0
  107. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_sys.py +0 -0
  108. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_tempfile.py +0 -0
  109. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_tenacity.py +0 -0
  110. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_text.py +0 -0
  111. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_threading.py +0 -0
  112. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_timer.py +0 -0
  113. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback.py +0 -0
  114. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
  115. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/chain.py +0 -0
  116. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  117. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  118. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  119. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/many.py +0 -0
  120. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/one.py +0 -0
  121. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
  122. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  123. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  124. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/two.py +0 -0
  125. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
  126. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_types.py +0 -0
  127. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_typing.py +0 -0
  128. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  129. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  130. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  131. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_tzdata.py +0 -0
  132. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_tzlocal.py +0 -0
  133. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_uuid.py +0 -0
  134. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_version.py +0 -0
  135. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_warnings.py +0 -0
  136. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_whenever.py +0 -0
  137. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_zipfile.py +0 -0
  138. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/tests/test_zoneinfo.py +0 -0
  139. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/altair.py +0 -0
  140. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/astor.py +0 -0
  141. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/atomicwrites.py +0 -0
  142. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/atools.py +0 -0
  143. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/cachetools.py +0 -0
  144. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/click.py +0 -0
  145. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/concurrent.py +0 -0
  146. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/contextlib.py +0 -0
  147. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/contextvars.py +0 -0
  148. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/cryptography.py +0 -0
  149. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/cvxpy.py +0 -0
  150. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/dataclasses.py +0 -0
  151. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/datetime.py +0 -0
  152. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/enum.py +0 -0
  153. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/errors.py +0 -0
  154. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/eventkit.py +0 -0
  155. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/fpdf2.py +0 -0
  156. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/functions.py +0 -0
  157. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/functools.py +0 -0
  158. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/getpass.py +0 -0
  159. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/git.py +0 -0
  160. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/hashlib.py +0 -0
  161. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/http.py +0 -0
  162. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/hypothesis.py +0 -0
  163. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/importlib.py +0 -0
  164. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/ipython.py +0 -0
  165. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/iterables.py +0 -0
  166. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/jupyter.py +0 -0
  167. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/lightweight_charts.py +0 -0
  168. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/logging.py +0 -0
  169. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/loguru.py +0 -0
  170. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/luigi.py +0 -0
  171. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/math.py +0 -0
  172. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/memory_profiler.py +0 -0
  173. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/modules.py +0 -0
  174. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/more_itertools.py +0 -0
  175. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/numpy.py +0 -0
  176. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/operator.py +0 -0
  177. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/optuna.py +0 -0
  178. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/orjson.py +0 -0
  179. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/os.py +0 -0
  180. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/parse.py +0 -0
  181. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pathlib.py +0 -0
  182. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/period.py +0 -0
  183. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pickle.py +0 -0
  184. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/platform.py +0 -0
  185. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/polars.py +0 -0
  186. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/polars_ols.py +0 -0
  187. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pqdm.py +0 -0
  188. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/py.typed +0 -0
  189. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pydantic.py +0 -0
  190. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pyinstrument.py +0 -0
  191. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pyrsistent.py +0 -0
  192. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pytest.py +0 -0
  193. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/pytest_regressions.py +0 -0
  194. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/python_dotenv.py +0 -0
  195. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/random.py +0 -0
  196. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/re.py +0 -0
  197. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/reprlib.py +0 -0
  198. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/rich.py +0 -0
  199. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/scipy.py +0 -0
  200. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/sentinel.py +0 -0
  201. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/shelve.py +0 -0
  202. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/slack_sdk.py +0 -0
  203. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/socket.py +0 -0
  204. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/sqlalchemy_polars.py +0 -0
  205. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/statsmodels.py +0 -0
  206. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/streamlit.py +0 -0
  207. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/sys.py +0 -0
  208. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/tempfile.py +0 -0
  209. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/tenacity.py +0 -0
  210. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/text.py +0 -0
  211. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/threading.py +0 -0
  212. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/timer.py +0 -0
  213. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/traceback.py +0 -0
  214. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/types.py +0 -0
  215. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/typing.py +0 -0
  216. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/tzdata.py +0 -0
  217. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/tzlocal.py +0 -0
  218. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/uuid.py +0 -0
  219. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/version.py +0 -0
  220. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/warnings.py +0 -0
  221. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/whenever.py +0 -0
  222. {dycw_utilities-0.121.1 → dycw_utilities-0.122.0}/src/utilities/zipfile.py +0 -0
  223. {dycw_utilities-0.121.1 → dycw_utilities-0.122.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.121.1
3
+ Version: 0.122.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.121.1"
95
+ version = "0.122.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.121.1"
337
+ current_version = "0.122.0"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -5,7 +5,7 @@ from dataclasses import dataclass, field
5
5
  from functools import partial
6
6
  from itertools import chain, count
7
7
  from re import search
8
- from typing import TYPE_CHECKING, Any, Self, cast, override
8
+ from typing import TYPE_CHECKING, Any, ClassVar, Self, cast, override
9
9
 
10
10
  from hypothesis import HealthCheck, Phase, given, settings
11
11
  from hypothesis.strategies import (
@@ -18,13 +18,12 @@ from hypothesis.strategies import (
18
18
  permutations,
19
19
  sampled_from,
20
20
  )
21
- from pytest import LogCaptureFixture, mark, raises
21
+ from pytest import LogCaptureFixture, mark, param, raises
22
22
 
23
23
  from utilities.asyncio import (
24
24
  EnhancedTaskGroup,
25
25
  InfiniteLooper,
26
26
  InfiniteQueueLooper,
27
- InfiniteQueueLooperError,
28
27
  UniquePriorityQueue,
29
28
  UniqueQueue,
30
29
  _InfiniteLooperDefaultEventError,
@@ -135,15 +134,146 @@ class TestGetEvent:
135
134
 
136
135
 
137
136
  class TestInfiniteLooper:
138
- @given(n=integers(10, 11), sleep_core=sampled_from([0.1, ("every", 0.1)]))
139
- async def test_main(self, *, n: int, sleep_core: DurationOrEveryDuration) -> None:
140
- class TrueError(BaseException): ...
137
+ sleep_restart_cases: ClassVar[list[Any]] = [
138
+ param(60.0, "for 0:01:00"),
139
+ param(MINUTE, "for 0:01:00"),
140
+ param(("every", 60), "until next 0:01:00"),
141
+ param(("every", MINUTE), "until next 0:01:00"),
142
+ ]
143
+
144
+ async def test_main_no_errors(self) -> None:
145
+ @dataclass(kw_only=True)
146
+ class Example(InfiniteLooper[None]):
147
+ counter: int = 0
148
+
149
+ @override
150
+ async def _initialize(self) -> None:
151
+ self.counter = 0
152
+
153
+ @override
154
+ async def _core(self) -> None:
155
+ self.counter += 1
156
+
157
+ async with timeout_dur(duration=1.0), Example(sleep_core=0.05) as looper:
158
+ pass
159
+ assert 15 <= looper.counter <= 25
160
+
161
+ async def test_main_with_errors(self) -> None:
162
+ class CustomError(Exception): ...
163
+
164
+ @dataclass(kw_only=True)
165
+ class Example(InfiniteLooper[None]):
166
+ initializations: int = 0
167
+ counter: int = 0
168
+ teardowns: int = 0
169
+
170
+ @override
171
+ async def _initialize(self) -> None:
172
+ self.initializations += 1
173
+ self.counter = 0
174
+
175
+ @override
176
+ async def _core(self) -> None:
177
+ self.counter += 1
178
+ if self.counter >= 5:
179
+ raise CustomError
141
180
 
142
- class FalseError(BaseException): ...
181
+ @override
182
+ async def _teardown(self) -> None:
183
+ self.teardowns += 1
184
+
185
+ async with (
186
+ timeout_dur(duration=1.0),
187
+ Example(sleep_core=0.05, sleep_restart=0.05) as looper,
188
+ ):
189
+ pass
190
+ assert 3 <= looper.initializations <= 5
191
+ assert 0 <= looper.counter <= 5
192
+ assert 3 <= looper.teardowns <= 5
193
+
194
+ async def test_cancelled_error(self) -> None:
195
+ @dataclass(kw_only=True)
196
+ class Example(InfiniteLooper[None]):
197
+ counter: int = 0
198
+
199
+ @override
200
+ async def _core(self) -> None:
201
+ self.counter += 1
202
+ if self.counter >= 5:
203
+ raise CancelledError
204
+
205
+ async with Example(sleep_core=0.05) as service:
206
+ pass
207
+ assert 5 <= service.counter <= 15
208
+
209
+ async def test_duration(self) -> None:
210
+ @dataclass(kw_only=True)
211
+ class Example(InfiniteLooper[None]):
212
+ counter: int = 0
213
+
214
+ @override
215
+ async def _initialize(self) -> None:
216
+ self.counter = 0
217
+
218
+ @override
219
+ async def _core(self) -> None:
220
+ self.counter += 1
221
+
222
+ async with Example(duration=1.0, sleep_core=0.05) as looper:
223
+ pass
224
+ assert 15 <= looper.counter <= 25
225
+
226
+ async def test_hashable(self) -> None:
227
+ @dataclass(kw_only=True, unsafe_hash=True)
228
+ class Example(InfiniteLooper[None]): ...
229
+
230
+ looper = Example(sleep_core=0.1)
231
+ _ = hash(looper)
232
+
233
+ async def test_nested_context_manager(self) -> None:
234
+ @dataclass(kw_only=True)
235
+ class Example(InfiniteLooper[None]):
236
+ running: bool = False
237
+
238
+ @override
239
+ async def _initialize(self) -> None:
240
+ self.running = True
241
+
242
+ @override
243
+ async def _teardown(self) -> None:
244
+ self.running = False
245
+
246
+ looper = Example()
247
+ for _ in range(2):
248
+ assert not looper.running
249
+ async with timeout_dur(duration=0.2), looper:
250
+ assert looper.running
251
+ async with timeout_dur(duration=0.1), looper:
252
+ assert looper.running
253
+ assert looper.running
254
+ assert not looper.running
255
+
256
+ def test_repr(self) -> None:
257
+ @dataclass(kw_only=True)
258
+ class Example(InfiniteLooper[None]):
259
+ counter: int = 0
260
+
261
+ looper = Example()
262
+ result = repr(looper)
263
+ expected = "TestInfiniteLooper.test_repr.<locals>.Example(counter=0)"
264
+ assert result == expected
265
+
266
+ @given(n=integers(10, 11))
267
+ async def test_setting_events(self, *, n: int) -> None:
268
+ class TrueError(Exception): ...
269
+
270
+ class FalseError(Exception): ...
143
271
 
144
272
  @dataclass(kw_only=True)
145
273
  class Example(InfiniteLooper[bool]):
146
274
  counter: int = 0
275
+ true_counter: int = 0
276
+ false_counter: int = 0
147
277
 
148
278
  @override
149
279
  async def _initialize(self) -> None:
@@ -155,28 +285,29 @@ class TestInfiniteLooper:
155
285
  if self.counter >= n:
156
286
  self._set_event(event=n % 2 == 0)
157
287
 
288
+ @override
289
+ def _error_upon_core(self, error: Exception, /) -> None:
290
+ if isinstance(error, TrueError):
291
+ self.true_counter += 1
292
+ elif isinstance(error, FalseError):
293
+ self.false_counter += 1
294
+
158
295
  @override
159
296
  def _yield_events_and_exceptions(
160
297
  self,
161
- ) -> Iterator[tuple[bool, MaybeType[BaseException]]]:
298
+ ) -> Iterator[tuple[bool, MaybeType[Exception]]]:
162
299
  yield (True, TrueError)
163
300
  yield (False, FalseError)
164
301
 
165
- looper = Example(sleep_core=sleep_core)
302
+ async with timeout_dur(duration=1.0), Example(sleep_core=0.05) as looper:
303
+ ...
166
304
  match n % 2 == 0:
167
305
  case True:
168
- with raises(TrueError):
169
- _ = await looper()
306
+ assert looper.true_counter >= 1, looper
307
+ assert looper.false_counter == 0
170
308
  case False:
171
- with raises(FalseError):
172
- _ = await looper()
173
-
174
- async def test_hashable(self) -> None:
175
- @dataclass(kw_only=True, unsafe_hash=True)
176
- class Example(InfiniteLooper[None]): ...
177
-
178
- looper = Example(sleep_core=0.1)
179
- _ = hash(looper)
309
+ assert looper.true_counter == 0
310
+ assert looper.false_counter >= 1
180
311
 
181
312
  async def test_with_coroutine_self_set_event(self) -> None:
182
313
  external: int = 0
@@ -208,10 +339,11 @@ class TestInfiniteLooper:
208
339
  def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
209
340
  yield partial(inc_external, self)
210
341
 
211
- looper = Example(sleep_core=0.05, sleep_restart=0.05)
212
- with raises(TimeoutError):
213
- async with timeout_dur(duration=1.0):
214
- await looper()
342
+ async with (
343
+ timeout_dur(duration=1.0),
344
+ Example(sleep_core=0.05, sleep_restart=0.05) as looper,
345
+ ):
346
+ ...
215
347
  assert 4 <= looper.initializations <= 6
216
348
  assert 0 <= looper.counter <= 7
217
349
  assert 16 <= external <= 21
@@ -242,33 +374,39 @@ class TestInfiniteLooper:
242
374
  def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
243
375
  yield dummy
244
376
 
245
- looper = Example(sleep_core=0.05, sleep_restart=0.05)
246
- with raises(TimeoutError):
247
- async with timeout_dur(duration=1.0):
248
- await looper()
377
+ async with (
378
+ timeout_dur(duration=1.0),
379
+ Example(sleep_core=0.05, sleep_restart=0.05) as looper,
380
+ ):
381
+ ...
249
382
  assert 3 <= looper.initializations <= 5
250
383
  assert 0 <= looper.counter <= 5
251
384
 
252
- @given(logger=just("logger") | none())
253
- async def test_with_coroutine_other_coroutine_error(
254
- self, *, logger: str | None
255
- ) -> None:
256
- class CustomError(Exception): ...
385
+ async def test_with_looper(self) -> None:
386
+ @dataclass(kw_only=True)
387
+ class Child(InfiniteLooper[None]):
388
+ counter: int = 0
257
389
 
258
- async def dummy() -> None:
259
- for i in count():
260
- if i >= 5:
261
- raise CustomError
262
- await sleep(0.05)
390
+ @override
391
+ async def _initialize(self) -> None:
392
+ self.counter = 0
393
+
394
+ @override
395
+ async def _core(self) -> None:
396
+ self.counter += 1
263
397
 
264
398
  @dataclass(kw_only=True)
265
- class Example(InfiniteLooper[None]):
266
- initializations: int = 0
399
+ class Parent(InfiniteLooper[None]):
267
400
  counter: int = 0
401
+ child: Child = field(init=False, repr=False)
402
+
403
+ @override
404
+ def __post_init__(self) -> None:
405
+ super().__post_init__()
406
+ self.child = Child(sleep_core=self.sleep_core)
268
407
 
269
408
  @override
270
409
  async def _initialize(self) -> None:
271
- self.initializations += 1
272
410
  self.counter = 0
273
411
 
274
412
  @override
@@ -276,15 +414,13 @@ class TestInfiniteLooper:
276
414
  self.counter += 1
277
415
 
278
416
  @override
279
- def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
280
- yield dummy
417
+ def _yield_loopers(self) -> Iterator[InfiniteLooper]:
418
+ yield self.child
281
419
 
282
- looper = Example(sleep_core=0.05, sleep_restart=0.05, logger=logger)
283
- with raises(CancelledError):
284
- async with timeout_dur(duration=1.0):
285
- await looper()
286
- assert 3 <= looper.initializations <= 5
287
- assert 1 <= looper.counter <= 6
420
+ async with timeout_dur(duration=1.0), Parent(sleep_core=0.05) as parent:
421
+ ...
422
+ assert 15 <= parent.counter <= 25
423
+ assert 15 <= parent.child.counter <= 25
288
424
 
289
425
  async def test_error_default_event(self) -> None:
290
426
  @dataclass(kw_only=True)
@@ -298,15 +434,7 @@ class TestInfiniteLooper:
298
434
  raise _InfiniteLooperDefaultEventError(looper=looper)
299
435
 
300
436
  @given(logger=just("logger") | none())
301
- @mark.parametrize(
302
- ("sleep_restart", "desc"),
303
- [
304
- (60.0, "for 0:01:00"),
305
- (MINUTE, "for 0:01:00"),
306
- (("every", 60), "until next 0:01:00"),
307
- (("every", MINUTE), "until next 0:01:00"),
308
- ],
309
- )
437
+ @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
310
438
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
311
439
  async def test_error_upon_initialize(
312
440
  self,
@@ -328,27 +456,20 @@ class TestInfiniteLooper:
328
456
  async def _core(self) -> None:
329
457
  raise NotImplementedError
330
458
 
331
- looper = Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger)
332
- with raises(TimeoutError):
333
- async with timeout_dur(duration=0.5):
334
- _ = await looper()
459
+ async with (
460
+ timeout_dur(duration=1.0),
461
+ Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger),
462
+ ):
463
+ ...
335
464
  if logger is not None:
336
465
  message = caplog.messages[0]
337
466
  expected = f"'Example' encountered 'CustomError()' whilst initializing; sleeping {desc}..."
338
467
  assert message == expected
339
468
 
340
469
  @given(logger=just("logger") | none())
341
- @mark.parametrize(
342
- ("sleep_restart", "desc"),
343
- [
344
- (60.0, "for 0:01:00"),
345
- (MINUTE, "for 0:01:00"),
346
- (("every", 60), "until next 0:01:00"),
347
- (("every", MINUTE), "until next 0:01:00"),
348
- ],
349
- )
470
+ @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
350
471
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
351
- async def test_error_group_upon_coroutines(
472
+ async def test_error_upon_core(
352
473
  self,
353
474
  *,
354
475
  sleep_restart: DurationOrEveryDuration,
@@ -364,15 +485,110 @@ class TestInfiniteLooper:
364
485
  async def _core(self) -> None:
365
486
  raise CustomError
366
487
 
367
- looper = Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger)
368
- with raises(TimeoutError):
369
- async with timeout_dur(duration=0.5):
370
- _ = await looper()
488
+ async with (
489
+ timeout_dur(duration=1.0),
490
+ Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger),
491
+ ):
492
+ ...
371
493
  if logger is not None:
372
494
  message = caplog.messages[0]
373
495
  expected = f"'Example' encountered 'CustomError()'; sleeping {desc}..."
374
496
  assert message == expected
375
497
 
498
+ @given(logger=just("logger") | none())
499
+ @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
500
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
501
+ async def test_error_upon_teardown(
502
+ self,
503
+ *,
504
+ sleep_restart: DurationOrEveryDuration,
505
+ desc: str,
506
+ logger: str | None,
507
+ caplog: LogCaptureFixture,
508
+ ) -> None:
509
+ class Custom1Error(Exception): ...
510
+
511
+ class Custom2Error(Exception): ...
512
+
513
+ @dataclass(kw_only=True)
514
+ class Example(InfiniteLooper[None]):
515
+ counter: int = 0
516
+
517
+ @override
518
+ async def _core(self) -> None:
519
+ self.counter += 1
520
+ if self.counter >= 5:
521
+ self._set_event()
522
+
523
+ @override
524
+ async def _teardown(self) -> None:
525
+ raise Custom2Error
526
+
527
+ @override
528
+ def _yield_events_and_exceptions(
529
+ self,
530
+ ) -> Iterator[tuple[None, MaybeType[Exception]]]:
531
+ yield (None, Custom1Error)
532
+
533
+ async with (
534
+ timeout_dur(duration=1.0),
535
+ Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger),
536
+ ):
537
+ ...
538
+ if logger is not None:
539
+ expected = f"'Example' encountered 'Custom2Error()' whilst tearing down; sleeping {desc}..."
540
+ assert expected in caplog.messages
541
+
542
+ @given(logger=just("logger") | none())
543
+ @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
544
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
545
+ async def test_error_group_upon_others(
546
+ self,
547
+ *,
548
+ sleep_restart: DurationOrEveryDuration,
549
+ desc: str,
550
+ logger: str | None,
551
+ caplog: LogCaptureFixture,
552
+ ) -> None:
553
+ class CustomError(Exception): ...
554
+
555
+ async def dummy() -> None:
556
+ for i in count():
557
+ if i >= 5:
558
+ raise CustomError
559
+ await sleep(0.05)
560
+
561
+ @dataclass(kw_only=True)
562
+ class Example(InfiniteLooper[None]):
563
+ initializations: int = 0
564
+ counter: int = 0
565
+
566
+ @override
567
+ async def _initialize(self) -> None:
568
+ self.initializations += 1
569
+ self.counter = 0
570
+
571
+ @override
572
+ async def _core(self) -> None:
573
+ self.counter += 1
574
+
575
+ @override
576
+ def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
577
+ yield dummy
578
+
579
+ async with (
580
+ timeout_dur(duration=1.0),
581
+ Example(sleep_core=0.05, sleep_restart=sleep_restart, logger=logger),
582
+ ):
583
+ ...
584
+ if logger is not None:
585
+ message = caplog.messages[0]
586
+ expected = f"""\
587
+ 'Example' encountered 1 error(s):
588
+ - Error #1/1: CustomError()
589
+ Sleeping {desc}..."""
590
+ assert message == expected
591
+
376
592
  async def test_error_no_event_found(self) -> None:
377
593
  @dataclass(kw_only=True)
378
594
  class Example(InfiniteLooper[None]):
@@ -388,36 +604,31 @@ class TestInfiniteLooper:
388
604
  if self.counter >= 10:
389
605
  self._set_event(event=cast("Any", "invalid"))
390
606
 
391
- looper = Example()
392
607
  with raises(
393
608
  _InfiniteLooperNoSuchEventError,
394
609
  match="'Example' does not have an event 'invalid'",
395
610
  ):
396
- _ = await looper()
611
+ async with Example():
612
+ ...
397
613
 
398
614
 
399
615
  class TestInfiniteQueueLooper:
400
616
  async def test_main(self) -> None:
401
617
  @dataclass(kw_only=True)
402
618
  class Example(InfiniteQueueLooper[None, int]):
403
- output: set[int] = field(default_factory=set)
619
+ counter: int = 0
404
620
 
405
621
  @override
406
622
  async def _process_items(self, *items: int) -> None:
407
- self.output.update(items)
623
+ self.counter += len(items)
408
624
 
409
- looper = Example(sleep_core=0.05)
410
-
411
- async def add_items() -> None:
412
- for i in count():
625
+ async with timeout_dur(duration=1.0), Example(sleep_core=0.05) as looper:
626
+ await sleep(0.1)
627
+ for i in range(10):
413
628
  looper.put_items_nowait(i)
414
629
  await sleep(0.05)
415
630
 
416
- with raises(ExceptionGroup): # noqa: PT012
417
- async with EnhancedTaskGroup(timeout=1.0) as tg:
418
- _ = tg.create_task(looper())
419
- _ = tg.create_task(add_items())
420
- assert 15 <= len(looper.output) <= 20
631
+ assert looper.counter == 10
421
632
 
422
633
  @given(n=integers(1, 10))
423
634
  def test_len_and_empty(self, *, n: int) -> None:
@@ -435,20 +646,6 @@ class TestInfiniteQueueLooper:
435
646
  assert len(looper) == n
436
647
  assert not looper.empty()
437
648
 
438
- async def test_no_items(self) -> None:
439
- @dataclass(kw_only=True)
440
- class Example(InfiniteQueueLooper[None, int]):
441
- output: set[int] = field(default_factory=set)
442
-
443
- @override
444
- async def _process_items(self, *items: int) -> None:
445
- self.output.update(items)
446
-
447
- looper = Example(sleep_core=0.05)
448
- with raises(TimeoutError):
449
- async with timeout_dur(duration=0.5):
450
- _ = await looper()
451
-
452
649
  async def test_run_until_empty(self) -> None:
453
650
  @dataclass(kw_only=True)
454
651
  class Example(InfiniteQueueLooper[None, int]):
@@ -458,27 +655,17 @@ class TestInfiniteQueueLooper:
458
655
  async def _process_items(self, *items: int) -> None:
459
656
  self.output.update(items)
460
657
 
461
- looper = Example(sleep_core=0.5)
462
-
463
- async def add_items() -> None:
464
- for i in count():
465
- looper.put_items_nowait(i)
466
- await sleep(0.01)
467
-
468
- with raises(ExceptionGroup): # noqa: PT012
469
- async with EnhancedTaskGroup(timeout=1.0) as tg:
470
- _ = tg.create_task(looper())
471
- _ = tg.create_task(add_items())
472
-
473
- tasks = len(looper)
474
- assert tasks >= 1
475
- await sleep(0.1)
476
- assert len(looper) == tasks
477
- await looper.run_until_empty()
658
+ looper = Example(sleep_core=0.05)
659
+ looper.put_items_nowait(*range(10))
660
+ async with looper:
661
+ await looper.run_until_empty()
478
662
  assert looper.empty()
479
663
 
480
664
  @given(logger=just("logger") | none())
481
- async def test_error_process_items(self, *, logger: str | None) -> None:
665
+ @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
666
+ async def test_error_process_items(
667
+ self, *, logger: str | None, caplog: LogCaptureFixture
668
+ ) -> None:
482
669
  class CustomError(Exception): ...
483
670
 
484
671
  @dataclass(kw_only=True)
@@ -489,28 +676,15 @@ class TestInfiniteQueueLooper:
489
676
  async def _process_items(self, *items: int) -> None:
490
677
  raise CustomError(*items)
491
678
 
492
- looper = Example(sleep_core=0.05, logger=logger)
493
- looper.put_items_nowait(1)
494
- with raises(TimeoutError):
495
- async with timeout_dur(duration=0.5):
496
- _ = await looper()
497
-
498
- async def test_error_infinite_queue_looper(self) -> None:
499
- class CustomError(Exception): ...
500
-
501
- @dataclass(kw_only=True)
502
- class Example(InfiniteQueueLooper[None, int]):
503
- @override
504
- async def _process_items(self, *items: int) -> None:
505
- raise CustomError(*items)
506
-
507
- looper = Example(sleep_core=0.1)
508
- looper.put_items_nowait(1)
509
- with raises(
510
- InfiniteQueueLooperError,
511
- match=r"'Example' encountered CustomError\(1\) whilst processing 1 item\(s\): \[1\]",
679
+ async with (
680
+ timeout_dur(duration=1.0),
681
+ Example(sleep_core=0.05, logger=logger) as looper,
512
682
  ):
513
- _ = await looper._core()
683
+ looper.put_items_nowait(1)
684
+ if logger is not None:
685
+ message = caplog.messages[0]
686
+ expected = "'Example' encountered CustomError(1) whilst processing 1 item(s) [1]; sleeping for 0:01:00..."
687
+ assert message == expected
514
688
 
515
689
 
516
690
  class TestPutAndGetItems:
@@ -3,10 +3,8 @@ from __future__ import annotations
3
3
  from asyncio import sleep
4
4
  from re import search
5
5
 
6
- from pytest import raises
7
-
8
6
  from tests.conftest import SKIPIF_CI
9
- from utilities.asyncio import EnhancedTaskGroup
7
+ from utilities.asyncio import timeout_dur
10
8
  from utilities.fastapi import PingReceiver
11
9
 
12
10
 
@@ -14,19 +12,14 @@ class TestPingReceiver:
14
12
  @SKIPIF_CI
15
13
  async def test_main(self) -> None:
16
14
  port = 5465
17
- receiver = PingReceiver(port=port)
18
15
  assert await PingReceiver.ping(port) is False
19
16
  await sleep(0.1)
20
-
21
- async def run_test() -> None:
17
+ async with timeout_dur(duration=1.0), PingReceiver(port=port):
22
18
  await sleep(0.1)
23
19
  result = await PingReceiver.ping(port)
24
20
  assert isinstance(result, str)
25
21
  assert search(
26
22
  r"pong @ \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6}", result
27
23
  )
28
-
29
- with raises(ExceptionGroup): # noqa: PT012
30
- async with EnhancedTaskGroup(timeout=1.0) as tg:
31
- _ = tg.create_task(receiver())
32
- _ = tg.create_task(run_test())
24
+ await sleep(0.1)
25
+ assert await PingReceiver.ping(port) is False