dycw-utilities 0.117.1__tar.gz → 0.118.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 (231) hide show
  1. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/pyproject.toml +2 -2
  3. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_asyncio.py +3 -389
  4. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_fastapi.py +11 -3
  5. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_redis.py +0 -75
  6. dycw_utilities-0.118.0/src/tests/test_slack_sdk.py +83 -0
  7. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_sqlalchemy.py +0 -30
  8. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/__init__.py +1 -1
  9. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/asyncio.py +2 -224
  10. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/fastapi.py +3 -8
  11. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/redis.py +1 -18
  12. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/slack_sdk.py +2 -68
  13. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/sqlalchemy.py +1 -44
  14. dycw_utilities-0.117.1/src/tests/scripts/test_async_service/__init__.py +0 -45
  15. dycw_utilities-0.117.1/src/tests/scripts/test_async_service/__main__.py +0 -6
  16. dycw_utilities-0.117.1/src/tests/scripts/test_async_service/run.sh +0 -3
  17. dycw_utilities-0.117.1/src/tests/scripts/test_queue_processor/__init__.py +0 -51
  18. dycw_utilities-0.117.1/src/tests/scripts/test_queue_processor/__main__.py +0 -6
  19. dycw_utilities-0.117.1/src/tests/scripts/test_queue_processor/run.sh +0 -3
  20. dycw_utilities-0.117.1/src/tests/test_slack_sdk.py +0 -190
  21. dycw_utilities-0.117.1/src/tests/test_traceback_funcs/__init__.py +0 -1
  22. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/.gitignore +0 -0
  23. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/LICENSE +0 -0
  24. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/README.md +0 -0
  25. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/__init__.py +0 -0
  26. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/conftest.py +0 -0
  27. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/__init__.py +0 -0
  28. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_missing/__init__.py +0 -0
  29. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_missing/module.py +0 -0
  30. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/__init__.py +0 -0
  31. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/outer_1.py +0 -0
  32. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/outer_2.py +0 -0
  33. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  34. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  35. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  36. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  37. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_without/__init__.py +0 -0
  38. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_without/module_1.py +0 -0
  39. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/package_without/module_2.py +0 -0
  40. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/standalone.py +0 -0
  41. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/modules/with_imports.py +0 -0
  42. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  43. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  44. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  45. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  46. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  47. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  48. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  49. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  50. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_altair.py +0 -0
  51. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_astor.py +0 -0
  52. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_atomicwrites.py +0 -0
  53. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_atools.py +0 -0
  54. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_cachetools.py +0 -0
  55. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_click.py +0 -0
  56. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_concurrent.py +0 -0
  57. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_contextlib.py +0 -0
  58. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_contextvars.py +0 -0
  59. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_cryptography.py +0 -0
  60. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_cvxpy.py +0 -0
  61. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_dataclasses.py +0 -0
  62. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_datetime.py +0 -0
  63. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_enum.py +0 -0
  64. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_errors.py +0 -0
  65. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_eventkit.py +0 -0
  66. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_fpdf2.py +0 -0
  67. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_functions.py +0 -0
  68. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_functools.py +0 -0
  69. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_getpass.py +0 -0
  70. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_git.py +0 -0
  71. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_hashlib.py +0 -0
  72. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_http.py +0 -0
  73. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_hypothesis.py +0 -0
  74. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_importlib.py +0 -0
  75. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_ipython.py +0 -0
  76. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_iterables.py +0 -0
  77. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_jupyter.py +0 -0
  78. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_lightweight_charts.py +0 -0
  79. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_logging.py +0 -0
  80. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_loguru.py +0 -0
  81. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_luigi.py +0 -0
  82. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_math.py +0 -0
  83. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_memory_profiler.py +0 -0
  84. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_modules.py +0 -0
  85. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_more_itertools.py +0 -0
  86. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_numpy.py +0 -0
  87. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_operator.py +0 -0
  88. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_optuna.py +0 -0
  89. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_orjson.py +0 -0
  90. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_os.py +0 -0
  91. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_parse.py +0 -0
  92. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pathlib.py +0 -0
  93. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_period.py +0 -0
  94. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pickle.py +0 -0
  95. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_platform.py +0 -0
  96. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_polars.py +0 -0
  97. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_polars_ols.py +0 -0
  98. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pqdm.py +0 -0
  99. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pydantic.py +0 -0
  100. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pyinstrument.py +0 -0
  101. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pyrsistent.py +0 -0
  102. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pytest.py +0 -0
  103. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_pytest_regressions.py +0 -0
  104. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_python_dotenv.py +0 -0
  105. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_random.py +0 -0
  106. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_re.py +0 -0
  107. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_reprlib.py +0 -0
  108. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_rich.py +0 -0
  109. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_scipy.py +0 -0
  110. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_sentinel.py +0 -0
  111. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_shelve.py +0 -0
  112. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_socket.py +0 -0
  113. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  114. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_statsmodel.py +0 -0
  115. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_streamlit.py +0 -0
  116. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_sys.py +0 -0
  117. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_tempfile.py +0 -0
  118. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_tenacity.py +0 -0
  119. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_text.py +0 -0
  120. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_threading.py +0 -0
  121. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_timer.py +0 -0
  122. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback.py +0 -0
  123. {dycw_utilities-0.117.1/src/tests/scripts → dycw_utilities-0.118.0/src/tests/test_traceback_funcs}/__init__.py +0 -0
  124. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/chain.py +0 -0
  125. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  126. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  127. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  128. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/many.py +0 -0
  129. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/one.py +0 -0
  130. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
  131. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  132. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  133. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/two.py +0 -0
  134. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
  135. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_types.py +0 -0
  136. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_typing.py +0 -0
  137. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  138. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  139. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  140. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_tzdata.py +0 -0
  141. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_tzlocal.py +0 -0
  142. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_uuid.py +0 -0
  143. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_version.py +0 -0
  144. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_warnings.py +0 -0
  145. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_whenever.py +0 -0
  146. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_zipfile.py +0 -0
  147. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/tests/test_zoneinfo.py +0 -0
  148. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/altair.py +0 -0
  149. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/astor.py +0 -0
  150. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/atomicwrites.py +0 -0
  151. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/atools.py +0 -0
  152. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/cachetools.py +0 -0
  153. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/click.py +0 -0
  154. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/concurrent.py +0 -0
  155. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/contextlib.py +0 -0
  156. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/contextvars.py +0 -0
  157. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/cryptography.py +0 -0
  158. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/cvxpy.py +0 -0
  159. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/dataclasses.py +0 -0
  160. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/datetime.py +0 -0
  161. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/enum.py +0 -0
  162. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/errors.py +0 -0
  163. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/eventkit.py +0 -0
  164. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/fpdf2.py +0 -0
  165. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/functions.py +0 -0
  166. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/functools.py +0 -0
  167. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/getpass.py +0 -0
  168. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/git.py +0 -0
  169. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/hashlib.py +0 -0
  170. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/http.py +0 -0
  171. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/hypothesis.py +0 -0
  172. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/importlib.py +0 -0
  173. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/ipython.py +0 -0
  174. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/iterables.py +0 -0
  175. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/jupyter.py +0 -0
  176. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/lightweight_charts.py +0 -0
  177. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/logging.py +0 -0
  178. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/loguru.py +0 -0
  179. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/luigi.py +0 -0
  180. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/math.py +0 -0
  181. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/memory_profiler.py +0 -0
  182. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/modules.py +0 -0
  183. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/more_itertools.py +0 -0
  184. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/numpy.py +0 -0
  185. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/operator.py +0 -0
  186. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/optuna.py +0 -0
  187. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/orjson.py +0 -0
  188. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/os.py +0 -0
  189. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/parse.py +0 -0
  190. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pathlib.py +0 -0
  191. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/period.py +0 -0
  192. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pickle.py +0 -0
  193. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/platform.py +0 -0
  194. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/polars.py +0 -0
  195. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/polars_ols.py +0 -0
  196. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pqdm.py +0 -0
  197. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/py.typed +0 -0
  198. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pydantic.py +0 -0
  199. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pyinstrument.py +0 -0
  200. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pyrsistent.py +0 -0
  201. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pytest.py +0 -0
  202. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/pytest_regressions.py +0 -0
  203. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/python_dotenv.py +0 -0
  204. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/random.py +0 -0
  205. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/re.py +0 -0
  206. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/reprlib.py +0 -0
  207. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/rich.py +0 -0
  208. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/scipy.py +0 -0
  209. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/sentinel.py +0 -0
  210. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/shelve.py +0 -0
  211. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/socket.py +0 -0
  212. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/sqlalchemy_polars.py +0 -0
  213. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/statsmodels.py +0 -0
  214. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/streamlit.py +0 -0
  215. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/sys.py +0 -0
  216. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/tempfile.py +0 -0
  217. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/tenacity.py +0 -0
  218. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/text.py +0 -0
  219. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/threading.py +0 -0
  220. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/timer.py +0 -0
  221. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/traceback.py +0 -0
  222. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/types.py +0 -0
  223. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/typing.py +0 -0
  224. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/tzdata.py +0 -0
  225. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/tzlocal.py +0 -0
  226. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/uuid.py +0 -0
  227. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/version.py +0 -0
  228. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/warnings.py +0 -0
  229. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/whenever.py +0 -0
  230. {dycw_utilities-0.117.1 → dycw_utilities-0.118.0}/src/utilities/zipfile.py +0 -0
  231. {dycw_utilities-0.117.1 → dycw_utilities-0.118.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.117.1
3
+ Version: 0.118.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.117.1"
95
+ version = "0.118.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.117.1"
337
+ current_version = "0.118.0"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -1,16 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from asyncio import (
4
- CancelledError,
5
- Event,
6
- PriorityQueue,
7
- Queue,
8
- TaskGroup,
9
- run,
10
- sleep,
11
- timeout,
12
- )
13
- from collections import Counter
3
+ from asyncio import CancelledError, Event, Queue, TaskGroup, run, sleep, timeout
14
4
  from dataclasses import dataclass, field
15
5
  from functools import partial
16
6
  from itertools import chain, count
@@ -28,18 +18,14 @@ from hypothesis.strategies import (
28
18
  permutations,
29
19
  sampled_from,
30
20
  )
31
- from pytest import LogCaptureFixture, approx, mark, param, raises
21
+ from pytest import LogCaptureFixture, mark, raises
32
22
 
33
23
  from utilities.asyncio import (
34
- AsyncLoopingService,
35
- AsyncService,
36
24
  EnhancedTaskGroup,
37
- ExceptionProcessor,
38
25
  InfiniteLooper,
39
26
  InfiniteLooperError,
40
27
  InfiniteQueueLooper,
41
28
  InfiniteQueueLooperError,
42
- QueueProcessor,
43
29
  UniquePriorityQueue,
44
30
  UniqueQueue,
45
31
  _DurationOrEvery,
@@ -73,200 +59,6 @@ if TYPE_CHECKING:
73
59
  from utilities.types import Coroutine1, Duration, MaybeCallableEvent, MaybeType
74
60
 
75
61
 
76
- class TestAsyncLoopingService:
77
- async def test_main(self) -> None:
78
- @dataclass(kw_only=True)
79
- class Example(AsyncLoopingService):
80
- counter: int = 0
81
-
82
- @override
83
- async def _run(self) -> None:
84
- self.counter += 1
85
-
86
- async with Example(duration=1.0, sleep=0.1) as service:
87
- pass
88
- assert 5 <= service.counter <= 15
89
-
90
- async def test_cancel(self) -> None:
91
- @dataclass(kw_only=True)
92
- class Example(AsyncLoopingService):
93
- counter: int = 0
94
-
95
- @override
96
- async def _run(self) -> None:
97
- self.counter += 1
98
- if self.counter >= 10:
99
- raise CancelledError
100
-
101
- async with Example(sleep=0.1) as service:
102
- pass
103
- assert 5 <= service.counter <= 15
104
-
105
- async def test_sleep_after_failure(self) -> None:
106
- @dataclass(kw_only=True)
107
- class Example(AsyncLoopingService):
108
- counter: int = 0
109
- errors: Counter[type[Exception]] = field(default_factory=Counter)
110
-
111
- @override
112
- async def _run(self) -> None:
113
- self.counter += 1
114
- if self.counter % 2 == 0:
115
- raise ValueError
116
-
117
- @override
118
- async def _run_failure(self, error: Exception, /) -> None:
119
- self.errors.update([type(error)])
120
-
121
- async with Example(duration=1.0, sleep=0.1) as service:
122
- pass
123
- assert 5 <= service.counter <= 15
124
- assert 3 <= service.errors[ValueError] <= 7
125
-
126
- async def test_failure(self) -> None:
127
- class CustomError(Exception): ...
128
-
129
- @dataclass(kw_only=True)
130
- class Example(AsyncLoopingService):
131
- counter: int = 0
132
- failed: bool = False
133
-
134
- @override
135
- async def _run(self) -> None:
136
- self.counter += 1
137
- if self.counter >= 5:
138
- raise CustomError
139
-
140
- with raises(CustomError):
141
- async with Example(sleep=0.1):
142
- pass
143
-
144
-
145
- class TestAsyncService:
146
- async def test_main(self) -> None:
147
- @dataclass(kw_only=True)
148
- class Example(AsyncService):
149
- running: bool = False
150
-
151
- @override
152
- async def _start(self) -> None:
153
- self.running = True
154
-
155
- @override
156
- async def stop(self) -> None:
157
- self.running = False
158
- await super().stop()
159
-
160
- service = Example(duration=0.1)
161
- for _ in range(2):
162
- assert not service.running
163
- async with service:
164
- assert service.running
165
- async with service:
166
- assert service.running
167
- assert service.running
168
- assert not service.running
169
-
170
- async def test_timeout(self) -> None:
171
- @dataclass(kw_only=True)
172
- class Example(AsyncService):
173
- running: bool = False
174
-
175
- @override
176
- async def _start(self) -> None:
177
- self.running = True
178
-
179
- @override
180
- async def stop(self) -> None:
181
- self.running = False
182
- await super().stop()
183
-
184
- service = Example()
185
- try:
186
- async with timeout_dur(duration=0.05), service:
187
- await sleep(0.1)
188
- except TimeoutError:
189
- assert not service.running
190
-
191
- @mark.parametrize(
192
- ("duration", "expected"),
193
- [
194
- param(0.5, approx(5, abs=1)),
195
- param(1.0, approx(10, abs=1)),
196
- param(1.5, 10),
197
- param(None, 10),
198
- ],
199
- )
200
- async def test_cancellation(
201
- self, *, duration: Duration | None, expected: int
202
- ) -> None:
203
- class Example(AsyncService):
204
- counter: int = 0
205
-
206
- @override
207
- async def _start(self) -> None:
208
- for _ in range(10):
209
- self.counter += 1
210
- await sleep(0.1)
211
- raise CancelledError
212
-
213
- async with Example(duration=duration) as service:
214
- ...
215
- assert service.counter == expected
216
-
217
- async def test_extra_context_managers(self) -> None:
218
- @dataclass(kw_only=True)
219
- class Inner(AsyncService):
220
- duration: Duration | None = 0.1
221
- running: bool = False
222
-
223
- @override
224
- async def _start(self) -> None:
225
- self.running = True
226
-
227
- @override
228
- async def stop(self) -> None:
229
- self.running = False
230
- await super().stop()
231
-
232
- @dataclass(kw_only=True)
233
- class Outer(AsyncService):
234
- duration: Duration | None = 0.1
235
- running: bool = False
236
- inner: Inner = field(default_factory=Inner, init=False, repr=False)
237
-
238
- @override
239
- async def _start(self) -> None:
240
- self.running = True
241
- _ = await self._stack.enter_async_context(self.inner)
242
-
243
- @override
244
- async def stop(self) -> None:
245
- self.running = False
246
- await super().stop()
247
-
248
- outer = Outer()
249
- for _ in range(2):
250
- assert not outer.running
251
- assert not outer.inner.running
252
- async with outer:
253
- assert outer.running
254
- assert outer.inner.running
255
- assert not outer.running
256
- assert not outer.inner.running
257
-
258
- def test_repr(self) -> None:
259
- class Example(AsyncService):
260
- @override
261
- async def _start(self) -> None:
262
- await sleep(0.01)
263
-
264
- service = Example()
265
- result = repr(service)
266
- expected = "TestAsyncService.test_repr.<locals>.Example(duration=None)"
267
- assert result == expected
268
-
269
-
270
62
  class TestEnhancedTaskGroup:
271
63
  async def test_max_tasks_disabled(self) -> None:
272
64
  with Timer() as timer:
@@ -305,18 +97,6 @@ class TestEnhancedTaskGroup:
305
97
  assert isinstance(error, CustomError)
306
98
 
307
99
 
308
- class TestExceptionProcessor:
309
- async def test_main(self) -> None:
310
- processor = ExceptionProcessor()
311
-
312
- class CustomError(Exception): ...
313
-
314
- with raises(CustomError): # noqa: PT012
315
- async with processor:
316
- processor.enqueue(CustomError)
317
- await sleep(0.1)
318
-
319
-
320
100
  class TestGetEvent:
321
101
  def test_event(self) -> None:
322
102
  event = Event()
@@ -794,169 +574,6 @@ class TestPutAndGetItemsNoWait:
794
574
  assert result == xs[:max_size]
795
575
 
796
576
 
797
- class TestQueueProcessor:
798
- async def test_one_processor_slow_tasks(self) -> None:
799
- @dataclass(kw_only=True)
800
- class Example(QueueProcessor[int]):
801
- output: set[int] = field(default_factory=set)
802
-
803
- @override
804
- async def _process_item(self, item: int, /) -> None:
805
- self.output.add(item)
806
-
807
- async with Example() as processor:
808
-
809
- async def add_tasks() -> None:
810
- for i in range(10):
811
- processor.enqueue(i)
812
- await sleep(0.1)
813
-
814
- async def run_until_empty() -> None:
815
- await sleep(0.5)
816
- await processor.run_until_empty()
817
-
818
- async with TaskGroup() as tg:
819
- _ = tg.create_task(add_tasks())
820
- _ = tg.create_task(run_until_empty())
821
-
822
- assert len(processor.output) == 10
823
-
824
- async def test_one_processor_slow_run(self) -> None:
825
- @dataclass(kw_only=True)
826
- class Example(QueueProcessor[int]):
827
- output: set[int] = field(default_factory=set)
828
-
829
- @override
830
- async def _process_item(self, item: int, /) -> None:
831
- self.output.add(item)
832
- await sleep(0.01)
833
-
834
- async with Example() as processor:
835
- processor.enqueue(*range(10))
836
- await processor.run_until_empty()
837
- assert len(processor.output) == 10
838
-
839
- @given(n=integers(1, 10))
840
- async def test_one_processor_continually_adding(self, *, n: int) -> None:
841
- @dataclass(kw_only=True)
842
- class Example(QueueProcessor[int]):
843
- output: set[int] = field(default_factory=set)
844
-
845
- @override
846
- async def _process_item(self, item: int, /) -> None:
847
- self.output.add(item)
848
-
849
- async with Example() as processor:
850
- for i in range(n):
851
- processor.enqueue(i)
852
- await sleep(0.01)
853
- assert len(processor.output) == n
854
-
855
- async def test_two_processors(self) -> None:
856
- @dataclass(kw_only=True)
857
- class First(QueueProcessor[int]):
858
- second: Second
859
- output: set[int] = field(default_factory=set)
860
-
861
- @override
862
- async def _process_item(self, item: int, /) -> None:
863
- self.second.enqueue(item)
864
- self.output.add(item)
865
- await sleep(0.1)
866
-
867
- @dataclass(kw_only=True)
868
- class Second(QueueProcessor[int]):
869
- output: set[int] = field(default_factory=set)
870
-
871
- @override
872
- async def _process_item(self, item: int, /) -> None:
873
- self.output.add(item)
874
- await sleep(0.01)
875
-
876
- async with Second() as second, First(second=second) as first:
877
-
878
- async def yield_tasks() -> None:
879
- first.enqueue(*range(10))
880
- await first.run_until_empty()
881
-
882
- await yield_tasks()
883
- assert len(first.output) == 10
884
- assert len(second.output) == 10
885
-
886
- @mark.parametrize("duration", [param(0.1), param(0.5), param(1.0), param(1.5)])
887
- async def test_cancellation(self, *, duration: float) -> None:
888
- @dataclass(kw_only=True)
889
- class Example(QueueProcessor[int]):
890
- output: set[int] = field(default_factory=set)
891
-
892
- @override
893
- async def _process_item(self, item: int, /) -> None:
894
- self.output.add(item)
895
- await sleep(0.1)
896
-
897
- async with Example(duration=duration) as processor:
898
- processor.enqueue(*range(10))
899
- assert processor.output == set(range(10))
900
-
901
- async def test_empty(self) -> None:
902
- class Example(QueueProcessor[int]):
903
- @override
904
- async def _process_item(self, item: int, /) -> None:
905
- _ = item
906
-
907
- processor = Example()
908
- assert processor.empty()
909
- processor.enqueue(0)
910
- assert not processor.empty()
911
-
912
- @given(n=integers(0, 10))
913
- async def test_get_items_nowait(self, *, n: int) -> None:
914
- @dataclass(kw_only=True)
915
- class Example(QueueProcessor[int]):
916
- output: set[int] = field(default_factory=set)
917
-
918
- @override
919
- async def _process_item(self, _: int, /) -> None:
920
- items = self._get_items_nowait()
921
- self.output.add(len(items))
922
-
923
- processor = Example()
924
- processor.enqueue(*range(n + 1))
925
- await processor._run()
926
- result = one(processor.output)
927
- assert result == n
928
-
929
- @given(n=integers(0, 10))
930
- async def test_len(self, *, n: int) -> None:
931
- class Example(QueueProcessor[int]):
932
- @override
933
- async def _process_item(self, item: int) -> None:
934
- _ = item
935
-
936
- processor = Example()
937
- assert len(processor) == 0
938
- processor.enqueue(*range(n))
939
- assert len(processor) == n
940
-
941
- @given(data=data(), texts=lists(text_ascii(min_size=1), min_size=1))
942
- async def test_priority_queue(self, *, data: DataObject, texts: list[str]) -> None:
943
- @dataclass(kw_only=True)
944
- class Example(QueueProcessor[tuple[int, str]]):
945
- output: set[str] = field(default_factory=set)
946
-
947
- @override
948
- async def _process_item(self, item: tuple[int, str]) -> None:
949
- _, text = item
950
- self.output.add(text)
951
-
952
- processor = Example(queue_type=PriorityQueue)
953
- items = data.draw(permutations(list(enumerate(texts))))
954
- processor.enqueue(*items)
955
- await processor._run()
956
- result = one(processor.output)
957
- assert result == texts[0]
958
-
959
-
960
577
  class TestUniquePriorityQueue:
961
578
  @given(data=data(), texts=lists(text_ascii(min_size=1), min_size=1, unique=True))
962
579
  async def test_main(self, *, data: DataObject, texts: list[str]) -> None:
@@ -1003,10 +620,7 @@ class TestSleepDur:
1003
620
 
1004
621
  class TestSleepUntil:
1005
622
  async def test_main(self) -> None:
1006
- now = get_now()
1007
- with Timer() as timer:
1008
- await sleep_until(now + 10 * MILLISECOND)
1009
- assert timer >= datetime_duration_to_timedelta(5 * MILLISECOND)
623
+ await sleep_until(get_now() + 10 * MILLISECOND)
1010
624
 
1011
625
 
1012
626
  class TestSleepUntilRounded:
@@ -3,7 +3,10 @@ from __future__ import annotations
3
3
  from asyncio import sleep
4
4
  from re import search
5
5
 
6
+ from pytest import raises
7
+
6
8
  from tests.conftest import SKIPIF_CI
9
+ from utilities.asyncio import EnhancedTaskGroup
7
10
  from utilities.fastapi import PingReceiver
8
11
 
9
12
 
@@ -11,14 +14,19 @@ class TestPingReceiver:
11
14
  @SKIPIF_CI
12
15
  async def test_main(self) -> None:
13
16
  port = 5465
17
+ receiver = PingReceiver(port=port)
14
18
  assert await PingReceiver.ping(port) is False
15
19
  await sleep(0.1)
16
- async with PingReceiver(port=port):
20
+
21
+ async def run_test() -> None:
17
22
  await sleep(0.1)
18
23
  result = await PingReceiver.ping(port)
19
24
  assert isinstance(result, str)
20
25
  assert search(
21
26
  r"pong @ \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6}", result
22
27
  )
23
- await sleep(0.1)
24
- assert await PingReceiver.ping(port) is False
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())
@@ -29,7 +29,6 @@ from utilities.hypothesis import (
29
29
  )
30
30
  from utilities.orjson import deserialize, serialize
31
31
  from utilities.redis import (
32
- Publisher,
33
32
  PublisherIQL,
34
33
  PublisherIQLError,
35
34
  publish,
@@ -119,80 +118,6 @@ class TestPublishAndSubscribe:
119
118
  _ = task.cancel()
120
119
 
121
120
 
122
- class TestPublisher:
123
- @given(
124
- data=data(),
125
- channel=text_ascii(min_size=1).map(
126
- lambda c: f"{get_class_name(TestPublisher)}_obj_ser_{c}"
127
- ),
128
- obj=make_objects(),
129
- )
130
- @mark.flaky
131
- @settings(
132
- max_examples=1,
133
- phases={Phase.generate},
134
- suppress_health_check={HealthCheck.function_scoped_fixture},
135
- )
136
- @SKIPIF_CI_AND_NOT_LINUX
137
- async def test_main(
138
- self, *, capsys: CaptureFixture, data: DataObject, channel: str, obj: Any
139
- ) -> None:
140
- async with yield_test_redis(data) as test:
141
-
142
- async def listener() -> None:
143
- async for msg in subscribe(
144
- test.redis.pubsub(), channel, deserializer=deserialize
145
- ):
146
- print(msg) # noqa: T201
147
-
148
- task = create_task(listener())
149
- await sleep(0.1)
150
-
151
- async with Publisher(redis=test.redis, serializer=serialize) as publisher:
152
- publisher.enqueue((channel, obj))
153
- await sleep(0.1)
154
-
155
- try:
156
- out = capsys.readouterr().out
157
- expected = f"{obj}\n"
158
- assert out == expected
159
- finally:
160
- _ = task.cancel()
161
-
162
- @given(
163
- data=data(),
164
- channel=text_ascii(min_size=1).map(
165
- lambda c: f"{get_class_name(TestPublisher)}_text_no_ser_{c}"
166
- ),
167
- text=text_ascii(min_size=1),
168
- )
169
- @settings(
170
- max_examples=1,
171
- phases={Phase.generate},
172
- suppress_health_check={HealthCheck.function_scoped_fixture},
173
- )
174
- @SKIPIF_CI_AND_NOT_LINUX
175
- async def test_text_without_serialize(
176
- self, *, capsys: CaptureFixture, data: DataObject, channel: str, text: str
177
- ) -> None:
178
- async with yield_test_redis(data) as test:
179
-
180
- async def listener() -> None:
181
- async for msg in subscribe(test.redis.pubsub(), channel):
182
- print(msg) # noqa: T201
183
-
184
- task = create_task(listener())
185
- await sleep(0.1)
186
- _ = await publish(test.redis, channel, text)
187
- await sleep(0.1)
188
- try:
189
- out = capsys.readouterr().out
190
- expected = f"{text.encode()}\n"
191
- assert out == expected
192
- finally:
193
- _ = task.cancel()
194
-
195
-
196
121
  class TestPublisherIQL:
197
122
  @given(
198
123
  data=data(),
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from asyncio import sleep
4
+ from logging import getLogger
5
+ from typing import TYPE_CHECKING
6
+
7
+ from aiohttp import InvalidUrlClientError
8
+ from pytest import mark, raises
9
+ from slack_sdk.webhook.async_client import AsyncWebhookClient
10
+
11
+ from utilities.asyncio import EnhancedTaskGroup, sleep_dur
12
+ from utilities.datetime import MINUTE
13
+ from utilities.os import get_env_var
14
+ from utilities.pytest import throttle
15
+ from utilities.slack_sdk import SlackHandlerIQL, _get_client, send_to_slack
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+ from pathlib import Path
20
+
21
+
22
+ class TestGetClient:
23
+ def test_main(self) -> None:
24
+ client = _get_client("url")
25
+ assert isinstance(client, AsyncWebhookClient)
26
+
27
+
28
+ class TestSendToSlack:
29
+ async def test_main(self) -> None:
30
+ with raises(InvalidUrlClientError, match="url"):
31
+ await send_to_slack("url", "message")
32
+
33
+ @mark.skipif(get_env_var("SLACK", nullable=True) is None, reason="'SLACK' not set")
34
+ @throttle(duration=5 * MINUTE)
35
+ async def test_real(self) -> None:
36
+ url = get_env_var("SLACK")
37
+ await send_to_slack(
38
+ url, f"message from {TestSendToSlack.test_real.__qualname__}"
39
+ )
40
+
41
+
42
+ class TestSlackHandlerIQL:
43
+ async def test_main(self, *, tmp_path: Path) -> None:
44
+ messages: Sequence[str] = []
45
+
46
+ async def sender(_: str, text: str, /) -> None:
47
+ await sleep(0.01)
48
+ messages.append(text)
49
+
50
+ logger = getLogger(str(tmp_path))
51
+ logger.addHandler(
52
+ handler := SlackHandlerIQL("url", sleep_core=0.05, sender=sender)
53
+ )
54
+
55
+ async def sleep_then_log() -> None:
56
+ await sleep_dur(duration=0.05)
57
+ logger.warning("message")
58
+
59
+ with raises(ExceptionGroup): # noqa: PT012
60
+ async with EnhancedTaskGroup(timeout=0.5) as tg:
61
+ _ = tg.create_task(handler())
62
+ _ = tg.create_task(sleep_then_log())
63
+
64
+ assert messages == ["message"]
65
+
66
+ @mark.skipif(get_env_var("SLACK", nullable=True) is None, reason="'SLACK' not set")
67
+ @throttle(duration=5 * MINUTE)
68
+ async def test_real(self, *, tmp_path: Path) -> None:
69
+ url = get_env_var("SLACK")
70
+ logger = getLogger(str(tmp_path))
71
+ logger.addHandler(handler := SlackHandlerIQL(url, sleep_core=0.05))
72
+
73
+ async def sleep_then_log() -> None:
74
+ await sleep_dur(duration=0.05)
75
+ for i in range(10):
76
+ logger.warning(
77
+ "message %d from %s", i, TestSlackHandlerIQL.test_real.__qualname__
78
+ )
79
+
80
+ with raises(ExceptionGroup): # noqa: PT012
81
+ async with EnhancedTaskGroup(timeout=0.5) as tg:
82
+ _ = tg.create_task(handler())
83
+ _ = tg.create_task(sleep_then_log())