dycw-utilities 0.126.9__tar.gz → 0.126.11__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 (233) hide show
  1. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/PKG-INFO +3 -3
  2. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/pyproject.toml +6 -5
  3. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_asyncio.py +83 -55
  4. dycw_utilities-0.126.11/src/tests/test_asyncio_classes/loopers.py +310 -0
  5. dycw_utilities-0.126.11/src/tests/test_asyncio_classes/redis.py +28 -0
  6. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_hypothesis.py +0 -8
  7. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_orjson.py +1 -1
  8. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pottery.py +3 -7
  9. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_redis.py +244 -198
  10. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_sqlalchemy.py +135 -156
  11. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_sqlalchemy_polars.py +38 -56
  12. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_text.py +7 -0
  13. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/__init__.py +1 -1
  14. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/hypothesis.py +1 -19
  15. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/redis.py +115 -5
  16. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/sqlalchemy.py +65 -1
  17. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/text.py +17 -0
  18. dycw_utilities-0.126.9/src/tests/test_asyncio_classes/loopers.py +0 -168
  19. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/.gitignore +0 -0
  20. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/LICENSE +0 -0
  21. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/README.md +0 -0
  22. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/__init__.py +0 -0
  23. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/conftest.py +0 -0
  24. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/__init__.py +0 -0
  25. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_missing/__init__.py +0 -0
  26. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_missing/module.py +0 -0
  27. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/__init__.py +0 -0
  28. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/outer_1.py +0 -0
  29. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/outer_2.py +0 -0
  30. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  31. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  32. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  33. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  34. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_without/__init__.py +0 -0
  35. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_without/module_1.py +0 -0
  36. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/package_without/module_2.py +0 -0
  37. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/standalone.py +0 -0
  38. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/modules/with_imports.py +0 -0
  39. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  40. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  41. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  42. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  43. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  44. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  45. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  46. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  47. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_altair.py +0 -0
  48. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_asyncio_classes/__init__.py +0 -0
  49. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_atomicwrites.py +0 -0
  50. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_atools.py +0 -0
  51. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_cachetools.py +0 -0
  52. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_click.py +0 -0
  53. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_concurrent.py +0 -0
  54. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_contextlib.py +0 -0
  55. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_contextvars.py +0 -0
  56. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_cryptography.py +0 -0
  57. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_cvxpy.py +0 -0
  58. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_dataclasses.py +0 -0
  59. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_datetime.py +0 -0
  60. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_enum.py +0 -0
  61. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_errors.py +0 -0
  62. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_eventkit.py +0 -0
  63. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_fastapi.py +0 -0
  64. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_fpdf2.py +0 -0
  65. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_functions.py +0 -0
  66. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_functools.py +0 -0
  67. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_getpass.py +0 -0
  68. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_git.py +0 -0
  69. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_hashlib.py +0 -0
  70. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_http.py +0 -0
  71. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_importlib.py +0 -0
  72. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_ipython.py +0 -0
  73. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_iterables.py +0 -0
  74. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_jupyter.py +0 -0
  75. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_libcst.py +0 -0
  76. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_lightweight_charts.py +0 -0
  77. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_logging.py +0 -0
  78. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_loguru.py +0 -0
  79. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_luigi.py +0 -0
  80. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_math.py +0 -0
  81. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_memory_profiler.py +0 -0
  82. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_modules.py +0 -0
  83. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_more_itertools.py +0 -0
  84. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_numpy.py +0 -0
  85. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_operator.py +0 -0
  86. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_optuna.py +0 -0
  87. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_os.py +0 -0
  88. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_parse.py +0 -0
  89. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pathlib.py +0 -0
  90. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_period.py +0 -0
  91. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pickle.py +0 -0
  92. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_platform.py +0 -0
  93. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_polars.py +0 -0
  94. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_polars_ols.py +0 -0
  95. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pqdm.py +0 -0
  96. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_psutil.py +0 -0
  97. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pydantic.py +0 -0
  98. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pyinstrument.py +0 -0
  99. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pyrsistent.py +0 -0
  100. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pytest.py +0 -0
  101. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_pytest_regressions.py +0 -0
  102. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_python_dotenv.py +0 -0
  103. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_random.py +0 -0
  104. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_re.py +0 -0
  105. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_reprlib.py +0 -0
  106. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_rich.py +0 -0
  107. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_scipy.py +0 -0
  108. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_sentinel.py +0 -0
  109. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_shelve.py +0 -0
  110. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_slack_sdk.py +0 -0
  111. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_socket.py +0 -0
  112. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_statsmodel.py +0 -0
  113. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_streamlit.py +0 -0
  114. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_string.py +0 -0
  115. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_sys.py +0 -0
  116. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_tempfile.py +0 -0
  117. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_tenacity.py +0 -0
  118. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_threading.py +0 -0
  119. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_timer.py +0 -0
  120. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback.py +0 -0
  121. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/__init__.py +0 -0
  122. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/chain.py +0 -0
  123. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  124. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  125. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  126. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/many.py +0 -0
  127. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/one.py +0 -0
  128. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/recursive.py +0 -0
  129. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  130. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  131. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/two.py +0 -0
  132. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_traceback_funcs/untraced.py +0 -0
  133. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_types.py +0 -0
  134. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_typing.py +0 -0
  135. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_typing_funcs/__init__.py +0 -0
  136. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_typing_funcs/no_future.py +0 -0
  137. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_typing_funcs/with_future.py +0 -0
  138. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_tzdata.py +0 -0
  139. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_tzlocal.py +0 -0
  140. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_uuid.py +0 -0
  141. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_version.py +0 -0
  142. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_warnings.py +0 -0
  143. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_whenever.py +0 -0
  144. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_zipfile.py +0 -0
  145. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/tests/test_zoneinfo.py +0 -0
  146. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/altair.py +0 -0
  147. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/asyncio.py +0 -0
  148. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/atomicwrites.py +0 -0
  149. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/atools.py +0 -0
  150. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/cachetools.py +0 -0
  151. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/click.py +0 -0
  152. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/concurrent.py +0 -0
  153. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/contextlib.py +0 -0
  154. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/contextvars.py +0 -0
  155. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/cryptography.py +0 -0
  156. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/cvxpy.py +0 -0
  157. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/dataclasses.py +0 -0
  158. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/datetime.py +0 -0
  159. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/enum.py +0 -0
  160. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/errors.py +0 -0
  161. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/eventkit.py +0 -0
  162. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/fastapi.py +0 -0
  163. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/fpdf2.py +0 -0
  164. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/functions.py +0 -0
  165. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/functools.py +0 -0
  166. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/getpass.py +0 -0
  167. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/git.py +0 -0
  168. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/hashlib.py +0 -0
  169. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/http.py +0 -0
  170. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/importlib.py +0 -0
  171. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/ipython.py +0 -0
  172. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/iterables.py +0 -0
  173. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/jupyter.py +0 -0
  174. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/libcst.py +0 -0
  175. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/lightweight_charts.py +0 -0
  176. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/logging.py +0 -0
  177. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/loguru.py +0 -0
  178. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/luigi.py +0 -0
  179. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/math.py +0 -0
  180. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/memory_profiler.py +0 -0
  181. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/modules.py +0 -0
  182. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/more_itertools.py +0 -0
  183. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/numpy.py +0 -0
  184. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/operator.py +0 -0
  185. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/optuna.py +0 -0
  186. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/orjson.py +0 -0
  187. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/os.py +0 -0
  188. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/parse.py +0 -0
  189. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pathlib.py +0 -0
  190. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/period.py +0 -0
  191. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pickle.py +0 -0
  192. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/platform.py +0 -0
  193. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/polars.py +0 -0
  194. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/polars_ols.py +0 -0
  195. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pottery.py +0 -0
  196. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pqdm.py +0 -0
  197. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/psutil.py +0 -0
  198. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/py.typed +0 -0
  199. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pydantic.py +0 -0
  200. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pyinstrument.py +0 -0
  201. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pyrsistent.py +0 -0
  202. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pytest.py +0 -0
  203. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/pytest_regressions.py +0 -0
  204. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/python_dotenv.py +0 -0
  205. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/random.py +0 -0
  206. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/re.py +0 -0
  207. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/reprlib.py +0 -0
  208. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/rich.py +0 -0
  209. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/scipy.py +0 -0
  210. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/sentinel.py +0 -0
  211. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/shelve.py +0 -0
  212. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/slack_sdk.py +0 -0
  213. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/socket.py +0 -0
  214. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/sqlalchemy_polars.py +0 -0
  215. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/statsmodels.py +0 -0
  216. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/streamlit.py +0 -0
  217. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/string.py +0 -0
  218. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/sys.py +0 -0
  219. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/tempfile.py +0 -0
  220. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/tenacity.py +0 -0
  221. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/threading.py +0 -0
  222. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/timer.py +0 -0
  223. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/traceback.py +0 -0
  224. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/types.py +0 -0
  225. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/typing.py +0 -0
  226. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/tzdata.py +0 -0
  227. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/tzlocal.py +0 -0
  228. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/uuid.py +0 -0
  229. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/version.py +0 -0
  230. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/warnings.py +0 -0
  231. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/whenever.py +0 -0
  232. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/zipfile.py +0 -0
  233. {dycw_utilities-0.126.9 → dycw_utilities-0.126.11}/src/utilities/zoneinfo.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.126.9
3
+ Version: 0.126.11
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: typing-extensions<4.14,>=4.13.1
8
8
  Provides-Extra: test
9
9
  Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
10
- Requires-Dist: hypothesis<6.133,>=6.132.0; extra == 'test'
10
+ Requires-Dist: hypothesis<6.134,>=6.133.0; extra == 'test'
11
11
  Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
12
12
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
13
13
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -77,7 +77,7 @@ Provides-Extra: zzz-test-hypothesis
77
77
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
78
78
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
79
79
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
80
- Requires-Dist: hypothesis<6.133,>=6.132.0; extra == 'zzz-test-hypothesis'
80
+ Requires-Dist: hypothesis<6.134,>=6.133.0; extra == 'zzz-test-hypothesis'
81
81
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-hypothesis'
83
83
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
@@ -25,7 +25,7 @@ dev = [
25
25
  "fpdf2 >= 2.8.3, < 2.9",
26
26
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
27
27
  "httpx >= 0.28.1, < 0.29", # for fastapi
28
- "hypothesis >= 6.132.0, < 6.133",
28
+ "hypothesis >= 6.133.0, < 6.134",
29
29
  "img2pdf >= 0.6.0, < 0.7",
30
30
  "lightweight-charts >= 2.1, < 2.2",
31
31
  "loguru >= 0.7.3, < 0.8",
@@ -94,12 +94,12 @@ dependencies = [
94
94
  name = "dycw-utilities"
95
95
  readme = "README.md"
96
96
  requires-python = ">= 3.12"
97
- version = "0.126.9"
97
+ version = "0.126.11"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
101
101
  "dycw-pytest-only >= 2.1.1, < 2.2",
102
- "hypothesis >= 6.132.0, < 6.133",
102
+ "hypothesis >= 6.133.0, < 6.134",
103
103
  "pytest >= 8.3.5, < 8.4",
104
104
  "pytest-asyncio >= 1.0.0, < 1.1",
105
105
  "pytest-cov >= 6.1.1, < 6.2",
@@ -174,7 +174,7 @@ zzz-test-hypothesis = [
174
174
  "aiosqlite >= 0.21.0, < 0.22",
175
175
  "asyncpg >= 0.30.0, < 0.31", # for sqlalchemy async
176
176
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
177
- "hypothesis >= 6.132.0, < 6.133",
177
+ "hypothesis >= 6.133.0, < 6.134",
178
178
  "luigi >= 3.6.0, < 3.7",
179
179
  "numpy >= 2.2.6, < 2.3",
180
180
  "pathvalidate >= 3.2.3, < 3.3",
@@ -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.126.9"
337
+ current_version = "0.126.11"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -436,6 +436,7 @@ addopts = [
436
436
  "--strict-markers",
437
437
  "--timeout=300",
438
438
  ]
439
+ asyncio_default_fixture_loop_scope = "function"
439
440
  asyncio_mode = "auto"
440
441
  filterwarnings = [
441
442
  "error",
@@ -21,16 +21,20 @@ from hypothesis.strategies import (
21
21
  permutations,
22
22
  sampled_from,
23
23
  )
24
- from pytest import LogCaptureFixture, approx, mark, param, raises
24
+ from pytest import LogCaptureFixture, mark, param, raises
25
25
 
26
26
  from tests.test_asyncio_classes.loopers import (
27
+ _REL,
27
28
  CountingLooper,
28
29
  CountingLooperError,
29
30
  LooperWithCounterMixin,
31
+ LooperWithCounterMixins,
30
32
  MultipleSubLoopers,
31
33
  Outer2CountingLooper,
32
34
  OuterCountingLooper,
33
35
  QueueLooper,
36
+ assert_looper_full,
37
+ assert_looper_stats,
34
38
  )
35
39
  from utilities.asyncio import (
36
40
  EnhancedQueue,
@@ -864,6 +868,7 @@ class TestInfiniteQueueLooper:
864
868
  assert looper.empty()
865
869
 
866
870
  @given(logger=just("logger") | none())
871
+ @mark.flaky
867
872
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
868
873
  async def test_error_process_items(
869
874
  self, *, logger: str | None, caplog: LogCaptureFixture
@@ -1007,6 +1012,70 @@ class TestLooper:
1007
1012
  ...
1008
1013
  self._assert_stats_half(looper._counter, stops=1)
1009
1014
 
1015
+ @mark.parametrize("counter_auto_start", [param(True), param(False)])
1016
+ async def test_mixin_in_task_group(self, *, counter_auto_start: bool) -> None:
1017
+ looper = LooperWithCounterMixin(
1018
+ auto_start=True, timeout=1.0, counter_auto_start=counter_auto_start
1019
+ )
1020
+ with raises(ExceptionGroup) as exc_info:
1021
+ async with EnhancedTaskGroup(timeout=looper.timeout) as tg:
1022
+ _ = tg.create_task_context(looper)
1023
+ error = one(exc_info.value.exceptions)
1024
+ assert isinstance(error, TimeoutError)
1025
+ self._assert_stats_half(looper._counter)
1026
+
1027
+ @mark.parametrize("counter1_auto_start", [param(True), param(False)])
1028
+ @mark.parametrize("counter2_auto_start", [param(True), param(False)])
1029
+ async def test_mixins(
1030
+ self, *, counter1_auto_start: bool, counter2_auto_start: bool
1031
+ ) -> None:
1032
+ looper = LooperWithCounterMixins(
1033
+ auto_start=True,
1034
+ timeout=1.0,
1035
+ counter1_auto_start=counter1_auto_start,
1036
+ counter2_auto_start=counter2_auto_start,
1037
+ )
1038
+ match counter1_auto_start, counter2_auto_start:
1039
+ case _, True:
1040
+ with raises(TimeoutError):
1041
+ async with timeout(1.0), looper:
1042
+ ...
1043
+ assert_looper_full(looper)
1044
+ self._assert_stats_no_runs(looper._counter1)
1045
+ self._assert_stats_third(looper._counter2)
1046
+ case True, False:
1047
+ with raises(TimeoutError):
1048
+ async with timeout(1.0), looper:
1049
+ ...
1050
+ assert_looper_full(looper)
1051
+ self._assert_stats_half(looper._counter1)
1052
+ self._assert_stats_third(looper._counter2)
1053
+ case False, False:
1054
+ async with looper:
1055
+ ...
1056
+ assert_looper_full(looper, stops=1)
1057
+ self._assert_stats_half(looper._counter1, stops=1)
1058
+ self._assert_stats_third(looper._counter2, stops=1)
1059
+
1060
+ @mark.parametrize("counter_auto_start", [param(True), param(False)])
1061
+ async def test_mixins_in_task_group(self, *, counter_auto_start: bool) -> None:
1062
+ looper1 = LooperWithCounterMixin(
1063
+ auto_start=True, timeout=1.0, counter_auto_start=counter_auto_start
1064
+ )
1065
+ looper2 = LooperWithCounterMixin(
1066
+ auto_start=True, timeout=1.0, counter_auto_start=counter_auto_start
1067
+ )
1068
+ with raises(ExceptionGroup) as exc_info: # noqa: PT012
1069
+ async with EnhancedTaskGroup(timeout=1.0) as tg:
1070
+ _ = tg.create_task_context(looper1)
1071
+ _ = tg.create_task_context(looper2)
1072
+ errors = exc_info.value.exceptions
1073
+ assert 1 <= len(errors) <= 2
1074
+ for error in errors:
1075
+ assert isinstance(error, TimeoutError)
1076
+ self._assert_stats_half(looper1._counter)
1077
+ self._assert_stats_half(looper2._counter)
1078
+
1010
1079
  def test_replace(self) -> None:
1011
1080
  looper = CountingLooper().replace(freq=10.0)
1012
1081
  assert looper.freq == 10.0
@@ -1024,7 +1093,7 @@ class TestLooper:
1024
1093
  looper = Example(auto_start=True, timeout=1.0)
1025
1094
  async with looper:
1026
1095
  ...
1027
- self._assert_stats(
1096
+ assert_looper_stats(
1028
1097
  looper,
1029
1098
  entries=1,
1030
1099
  core_successes=79,
@@ -1058,7 +1127,7 @@ class TestLooper:
1058
1127
  looper = Example(auto_start=True, timeout=1.0)
1059
1128
  async with looper:
1060
1129
  ...
1061
- self._assert_stats(
1130
+ assert_looper_stats(
1062
1131
  looper,
1063
1132
  entries=1,
1064
1133
  core_successes=14,
@@ -1098,7 +1167,7 @@ class TestLooper:
1098
1167
  looper.put_right_nowait(i)
1099
1168
  async with looper:
1100
1169
  ...
1101
- self._assert_stats(
1170
+ assert_looper_stats(
1102
1171
  looper,
1103
1172
  entries=1,
1104
1173
  core_successes=25,
@@ -1301,47 +1370,6 @@ class TestLooper:
1301
1370
  assert not looper.auto_start
1302
1371
  assert looper.with_auto_start.auto_start
1303
1372
 
1304
- def _assert_stats(
1305
- self,
1306
- looper: Looper[Any],
1307
- /,
1308
- *,
1309
- entries: int = 0,
1310
- core_successes: int = 0,
1311
- core_failures: int = 0,
1312
- initialization_successes: int = 0,
1313
- initialization_failures: int = 0,
1314
- tear_down_successes: int = 0,
1315
- tear_down_failures: int = 0,
1316
- restart_successes: int = 0,
1317
- restart_failures: int = 0,
1318
- stops: int = 0,
1319
- rel: float = 0.5,
1320
- ) -> None:
1321
- stats = looper.stats
1322
- assert stats.entries == entries
1323
- assert stats.core_attempts == (stats.core_successes + stats.core_failures)
1324
- assert stats.core_successes == approx(core_successes, rel=rel)
1325
- assert stats.core_failures == approx(core_failures, rel=rel)
1326
- assert stats.initialization_attempts == (
1327
- stats.initialization_successes + stats.initialization_failures
1328
- )
1329
- assert stats.initialization_successes == approx(
1330
- initialization_successes, rel=rel
1331
- )
1332
- assert stats.initialization_failures == approx(initialization_failures, rel=rel)
1333
- assert stats.tear_down_attempts == (
1334
- stats.tear_down_successes + stats.tear_down_failures
1335
- )
1336
- assert stats.tear_down_successes == approx(tear_down_successes, rel=rel)
1337
- assert stats.tear_down_failures == approx(tear_down_failures, rel=rel)
1338
- assert stats.restart_attempts == (
1339
- stats.restart_successes + stats.restart_failures
1340
- )
1341
- assert stats.restart_successes == approx(restart_successes, rel=rel)
1342
- assert stats.restart_failures == approx(restart_failures, rel=rel)
1343
- assert stats.stops == stops
1344
-
1345
1373
  def _assert_stats_no_runs(
1346
1374
  self,
1347
1375
  looper: Looper[Any],
@@ -1349,14 +1377,14 @@ class TestLooper:
1349
1377
  *,
1350
1378
  entries: int = 0,
1351
1379
  stops: int = 0,
1352
- rel: float = 0.75,
1380
+ rel: float = _REL,
1353
1381
  ) -> None:
1354
- self._assert_stats(looper, entries=entries, stops=stops, rel=rel)
1382
+ assert_looper_stats(looper, entries=entries, stops=stops, rel=rel)
1355
1383
 
1356
1384
  def _assert_stats_full(
1357
- self, looper: Looper[Any], /, *, stops: int = 0, rel: float = 0.75
1385
+ self, looper: Looper[Any], /, *, stops: int = 0, rel: float = _REL
1358
1386
  ) -> None:
1359
- self._assert_stats(
1387
+ assert_looper_stats(
1360
1388
  looper,
1361
1389
  entries=1,
1362
1390
  core_successes=45,
@@ -1369,9 +1397,9 @@ class TestLooper:
1369
1397
  )
1370
1398
 
1371
1399
  def _assert_stats_half(
1372
- self, looper: Looper[Any], /, *, stops: int = 0, rel: float = 0.75
1400
+ self, looper: Looper[Any], /, *, stops: int = 0, rel: float = _REL
1373
1401
  ) -> None:
1374
- self._assert_stats(
1402
+ assert_looper_stats(
1375
1403
  looper,
1376
1404
  entries=1,
1377
1405
  core_successes=56,
@@ -1384,9 +1412,9 @@ class TestLooper:
1384
1412
  )
1385
1413
 
1386
1414
  def _assert_stats_third(
1387
- self, looper: Looper[Any], /, *, stops: int = 0, rel: float = 0.75
1415
+ self, looper: Looper[Any], /, *, stops: int = 0, rel: float = _REL
1388
1416
  ) -> None:
1389
- self._assert_stats(
1417
+ assert_looper_stats(
1390
1418
  looper,
1391
1419
  entries=1,
1392
1420
  core_successes=49,
@@ -1399,9 +1427,9 @@ class TestLooper:
1399
1427
  )
1400
1428
 
1401
1429
  def _assert_stats_quarter(
1402
- self, looper: Looper[Any], /, *, stops: int = 0, rel: float = 0.75
1430
+ self, looper: Looper[Any], /, *, stops: int = 0, rel: float = _REL
1403
1431
  ) -> None:
1404
- self._assert_stats(
1432
+ assert_looper_stats(
1405
1433
  looper,
1406
1434
  entries=1,
1407
1435
  core_successes=35,
@@ -0,0 +1,310 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Any, Literal, override
5
+
6
+ from pytest import approx
7
+
8
+ from utilities.asyncio import Looper
9
+ from utilities.contextlib import suppress_super_object_attribute_error
10
+ from utilities.datetime import MILLISECOND
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
15
+ from utilities.types import Duration
16
+
17
+ _FREQ: Duration = 10 * MILLISECOND
18
+ _BACKOFF: Duration = 100 * MILLISECOND
19
+ _REL: float = 0.75
20
+
21
+
22
+ # assert
23
+
24
+
25
+ def assert_looper_stats(
26
+ looper: Looper[Any],
27
+ /,
28
+ *,
29
+ entries: int = 0,
30
+ core_successes: int | tuple[Literal[">="], int] = 0,
31
+ core_failures: int = 0,
32
+ initialization_successes: int = 0,
33
+ initialization_failures: int = 0,
34
+ tear_down_successes: int = 0,
35
+ tear_down_failures: int = 0,
36
+ restart_successes: int = 0,
37
+ restart_failures: int = 0,
38
+ stops: int = 0,
39
+ rel: float = _REL,
40
+ ) -> None:
41
+ stats = looper.stats
42
+ assert stats.entries == entries, f"{stats=}, {entries=}"
43
+ assert stats.core_attempts == (stats.core_successes + stats.core_failures), (
44
+ f"{stats=}"
45
+ )
46
+ match core_successes:
47
+ case int():
48
+ assert stats.core_successes == approx(core_successes, rel=rel), (
49
+ f"{stats=}, {core_successes=}"
50
+ )
51
+ case ">=", int() as min_successes:
52
+ assert stats.core_successes >= min_successes, f"{stats=}, {min_successes=}"
53
+ assert stats.core_failures == approx(core_failures, rel=rel), (
54
+ f"{stats=}, {core_failures=}"
55
+ )
56
+ assert stats.initialization_attempts == (
57
+ stats.initialization_successes + stats.initialization_failures
58
+ ), f"{stats=}"
59
+ assert stats.initialization_successes == approx(
60
+ initialization_successes, rel=rel
61
+ ), f"{stats=}, {initialization_successes=}"
62
+ assert stats.initialization_failures == approx(initialization_failures, rel=rel), (
63
+ f"{stats=}, {initialization_failures=}"
64
+ )
65
+ assert stats.tear_down_attempts == (
66
+ stats.tear_down_successes + stats.tear_down_failures
67
+ ), f"{stats=}"
68
+ assert stats.tear_down_successes == approx(tear_down_successes, rel=rel), (
69
+ f"{stats=}, {tear_down_successes=}"
70
+ )
71
+ assert stats.tear_down_failures == approx(tear_down_failures, rel=rel), (
72
+ f"{stats=}, {initialization_failures=}"
73
+ )
74
+ assert stats.restart_attempts == (
75
+ stats.restart_successes + stats.restart_failures
76
+ ), f"{stats=}"
77
+ assert stats.restart_successes == approx(restart_successes, rel=rel), (
78
+ f"{stats=}, {restart_successes=}"
79
+ )
80
+ assert stats.restart_failures == approx(restart_failures, rel=rel), (
81
+ f"{stats=}, {restart_failures=}"
82
+ )
83
+ assert stats.stops == stops, f"{stats=}, {stops=}"
84
+
85
+
86
+ def assert_looper_full(
87
+ looper: Looper[Any], /, *, stops: int = 0, rel: float = _REL
88
+ ) -> None:
89
+ assert_looper_stats(
90
+ looper,
91
+ entries=1,
92
+ core_successes=99,
93
+ initialization_successes=1,
94
+ stops=stops,
95
+ rel=rel,
96
+ )
97
+
98
+
99
+ # counting looper
100
+
101
+
102
+ @dataclass(kw_only=True)
103
+ class CountingLooper(Looper[Any]):
104
+ freq: Duration = field(default=_FREQ, repr=False)
105
+ backoff: Duration = field(default=_BACKOFF, repr=False)
106
+ _debug: bool = field(default=True, repr=False)
107
+ count: int = 0
108
+ max_count: int = 10
109
+
110
+ @override
111
+ async def _initialize_core(self) -> None:
112
+ await super()._initialize_core()
113
+ self.count = 0
114
+
115
+ @override
116
+ async def core(self) -> None:
117
+ await super().core()
118
+ self.count += 1
119
+ if self.count >= self.max_count:
120
+ raise CountingLooperError
121
+
122
+
123
+ class CountingLooperError(Exception): ...
124
+
125
+
126
+ # one sub looper
127
+
128
+
129
+ @dataclass(kw_only=True)
130
+ class OuterCountingLooper(CountingLooper):
131
+ inner: CountingLooper = field(init=False, repr=False)
132
+ inner_auto_start: bool = False
133
+
134
+ @override
135
+ def __post_init__(self) -> None:
136
+ super().__post_init__()
137
+ self.inner = CountingLooper(
138
+ auto_start=self.inner_auto_start,
139
+ freq=self.freq / 2,
140
+ backoff=self.backoff / 2,
141
+ max_count=round(self.max_count / 2),
142
+ )
143
+
144
+ @override
145
+ def _yield_sub_loopers(self) -> Iterator[Looper]:
146
+ yield from super()._yield_sub_loopers()
147
+ yield self.inner
148
+
149
+
150
+ # two sub loopers
151
+
152
+
153
+ @dataclass(kw_only=True)
154
+ class MultipleSubLoopers(CountingLooper):
155
+ inner1: CountingLooper = field(init=False, repr=False)
156
+ inner2: CountingLooper = field(init=False, repr=False)
157
+ inner1_auto_start: bool = False
158
+ inner2_auto_start: bool = False
159
+
160
+ @override
161
+ def __post_init__(self) -> None:
162
+ super().__post_init__()
163
+ self.inner1 = CountingLooper(
164
+ auto_start=self.inner1_auto_start,
165
+ freq=self.freq / 2,
166
+ backoff=self.backoff / 2,
167
+ max_count=round(self.max_count / 2),
168
+ )
169
+ self.inner2 = CountingLooper(
170
+ auto_start=self.inner2_auto_start,
171
+ freq=self.freq / 3,
172
+ backoff=self.backoff / 3,
173
+ max_count=round(self.max_count / 3),
174
+ )
175
+
176
+ @override
177
+ def _yield_sub_loopers(self) -> Iterator[Looper]:
178
+ yield from super()._yield_sub_loopers()
179
+ yield self.inner1
180
+ yield self.inner2
181
+
182
+
183
+ # nested sub loopers
184
+
185
+
186
+ @dataclass(kw_only=True)
187
+ class Outer2CountingLooper(CountingLooper):
188
+ middle: OuterCountingLooper = field(init=False, repr=False)
189
+ middle_auto_start: bool = False
190
+ inner_auto_start: bool = False
191
+
192
+ @override
193
+ def __post_init__(self) -> None:
194
+ super().__post_init__()
195
+ self.middle = OuterCountingLooper(
196
+ auto_start=self.middle_auto_start,
197
+ freq=self.freq / 2,
198
+ backoff=self.backoff / 2,
199
+ max_count=round(self.max_count / 2),
200
+ inner_auto_start=self.inner_auto_start,
201
+ )
202
+
203
+ @override
204
+ def _yield_sub_loopers(self) -> Iterator[Looper]:
205
+ yield from super()._yield_sub_loopers()
206
+ yield self.middle
207
+
208
+
209
+ # one mixin
210
+
211
+
212
+ @dataclass(kw_only=True)
213
+ class CounterMixin:
214
+ freq: Duration = field(default=_FREQ, repr=False)
215
+ backoff: Duration = field(default=_BACKOFF, repr=False)
216
+ _debug: bool = field(default=True, repr=False)
217
+ count: int = 0
218
+ max_count: int = 10
219
+ counter_auto_start: bool = False
220
+ _counter: CountingLooper = field(init=False, repr=False)
221
+
222
+ def __post_init__(self) -> None:
223
+ with suppress_super_object_attribute_error():
224
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
225
+ self._counter = CountingLooper(
226
+ auto_start=self.counter_auto_start,
227
+ freq=self.freq / 2,
228
+ backoff=self.backoff / 2,
229
+ max_count=round(self.max_count / 2),
230
+ )
231
+
232
+ def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
233
+ with suppress_super_object_attribute_error():
234
+ yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
235
+ yield self._counter
236
+
237
+
238
+ @dataclass(kw_only=True)
239
+ class LooperWithCounterMixin(CounterMixin, Looper): ...
240
+
241
+
242
+ # two mixins
243
+
244
+
245
+ @dataclass(kw_only=True)
246
+ class CounterMixin1:
247
+ freq: Duration = field(default=_FREQ, repr=False)
248
+ backoff: Duration = field(default=_BACKOFF, repr=False)
249
+ _debug: bool = field(default=True, repr=False)
250
+ count: int = 0
251
+ max_count: int = 10
252
+ counter1_auto_start: bool = False
253
+ _counter1: CountingLooper = field(init=False, repr=False)
254
+
255
+ def __post_init__(self) -> None:
256
+ with suppress_super_object_attribute_error():
257
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
258
+ self._counter1 = CountingLooper(
259
+ auto_start=self.counter1_auto_start,
260
+ freq=self.freq / 2,
261
+ backoff=self.backoff / 2,
262
+ max_count=round(self.max_count / 2),
263
+ )
264
+
265
+ def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
266
+ with suppress_super_object_attribute_error():
267
+ yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
268
+ yield self._counter1
269
+
270
+
271
+ @dataclass(kw_only=True)
272
+ class CounterMixin2:
273
+ freq: Duration = field(default=_FREQ, repr=False)
274
+ backoff: Duration = field(default=_BACKOFF, repr=False)
275
+ _debug: bool = field(default=True, repr=False)
276
+ count: int = 0
277
+ max_count: int = 10
278
+ counter2_auto_start: bool = False
279
+ _counter2: CountingLooper = field(init=False, repr=False)
280
+
281
+ def __post_init__(self) -> None:
282
+ with suppress_super_object_attribute_error():
283
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
284
+ self._counter2 = CountingLooper(
285
+ auto_start=self.counter2_auto_start,
286
+ freq=self.freq / 3,
287
+ backoff=self.backoff / 3,
288
+ max_count=round(self.max_count / 3),
289
+ )
290
+
291
+ def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
292
+ with suppress_super_object_attribute_error():
293
+ yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
294
+ yield self._counter2
295
+
296
+
297
+ @dataclass(kw_only=True)
298
+ class LooperWithCounterMixins(CounterMixin1, CounterMixin2, Looper): ...
299
+
300
+
301
+ # queue looper
302
+
303
+
304
+ @dataclass(kw_only=True)
305
+ class QueueLooper(Looper[int]):
306
+ @override
307
+ async def core(self) -> None:
308
+ await super().core()
309
+ if not self.empty():
310
+ _ = self.get_left_nowait()
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from tests.test_asyncio_classes.loopers import _BACKOFF, _FREQ
7
+ from utilities.asyncio import Looper
8
+ from utilities.redis import PublishServiceMixin, SubscribeServiceMixin
9
+ from utilities.text import unique_str
10
+
11
+ if TYPE_CHECKING:
12
+ from utilities.types import Duration
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class LooperWithPublishAndSubscribeMixins(
17
+ PublishServiceMixin[Any], SubscribeServiceMixin[Any], Looper[Any]
18
+ ):
19
+ freq: Duration = field(default=_FREQ, repr=False)
20
+ backoff: Duration = field(default=_BACKOFF, repr=False)
21
+ _debug: bool = field(default=True, repr=False)
22
+ publish_service_freq: Duration = field(default=_FREQ, repr=False)
23
+ publish_service_backoff: Duration = field(default=_BACKOFF, repr=False)
24
+ publish_service_debug: bool = field(default=True, repr=False)
25
+ subscribe_service_freq: Duration = field(default=_FREQ, repr=False)
26
+ subscribe_service_backoff: Duration = field(default=_BACKOFF, repr=False)
27
+ subscribe_service_debug: bool = field(default=True, repr=False)
28
+ subscribe_service_channel: str = field(default_factory=unique_str)
@@ -99,7 +99,6 @@ from utilities.hypothesis import (
99
99
  triples,
100
100
  uint32s,
101
101
  uint64s,
102
- unique_strs,
103
102
  versions,
104
103
  zoned_datetimes,
105
104
  )
@@ -1171,13 +1170,6 @@ class TestUInt64s:
1171
1170
  assert max(min_value, MIN_UINT64) <= x <= min(max_value, MAX_UINT64)
1172
1171
 
1173
1172
 
1174
- class TestUniqueStrs:
1175
- @given(data=data())
1176
- def test_main(self, *, data: DataObject) -> None:
1177
- first, second = data.draw(pairs(unique_strs()))
1178
- assert first != second
1179
-
1180
-
1181
1173
  class TestVersions:
1182
1174
  @given(data=data(), suffix=booleans())
1183
1175
  def test_main(self, *, data: DataObject, suffix: bool) -> None:
@@ -234,7 +234,7 @@ class TestGetLogRecords:
234
234
  handler.setFormatter(OrjsonFormatter())
235
235
  for level_, message_, extra_ in items:
236
236
  _ = assume(set(extra_) & set(_LOG_RECORD_DEFAULT_ATTRS) == set())
237
- logger.log(get_logging_level_number(level_), message_, extra=extra_)
237
+ logger.log(get_logging_level_number(level_), "%s", message_, extra=extra_)
238
238
  output = get_log_records(root, parallelism="threads")
239
239
  output = output.filter(
240
240
  index=index,