dycw-utilities 0.125.26__tar.gz → 0.126.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/PKG-INFO +1 -1
  2. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/pyproject.toml +2 -2
  3. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_asyncio.py +1 -2
  4. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_redis.py +319 -203
  5. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/redis.py +268 -77
  7. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/.gitignore +0 -0
  8. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/LICENSE +0 -0
  9. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/README.md +0 -0
  10. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/__init__.py +0 -0
  11. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/conftest.py +0 -0
  12. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/__init__.py +0 -0
  13. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_missing/__init__.py +0 -0
  14. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_missing/module.py +0 -0
  15. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/__init__.py +0 -0
  16. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/outer_1.py +0 -0
  17. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/outer_2.py +0 -0
  18. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  19. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  20. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  21. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  22. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_without/__init__.py +0 -0
  23. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_without/module_1.py +0 -0
  24. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/package_without/module_2.py +0 -0
  25. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/standalone.py +0 -0
  26. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/modules/with_imports.py +0 -0
  27. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  28. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  29. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  30. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  31. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  32. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  33. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  34. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  35. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_altair.py +0 -0
  36. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_atomicwrites.py +0 -0
  37. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_atools.py +0 -0
  38. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_cachetools.py +0 -0
  39. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_click.py +0 -0
  40. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_concurrent.py +0 -0
  41. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_contextlib.py +0 -0
  42. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_contextvars.py +0 -0
  43. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_cryptography.py +0 -0
  44. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_cvxpy.py +0 -0
  45. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_dataclasses.py +0 -0
  46. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_datetime.py +0 -0
  47. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_enum.py +0 -0
  48. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_errors.py +0 -0
  49. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_eventkit.py +0 -0
  50. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_fastapi.py +0 -0
  51. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_fpdf2.py +0 -0
  52. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_functions.py +0 -0
  53. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_functools.py +0 -0
  54. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_getpass.py +0 -0
  55. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_git.py +0 -0
  56. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_hashlib.py +0 -0
  57. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_http.py +0 -0
  58. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_hypothesis.py +0 -0
  59. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_importlib.py +0 -0
  60. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_ipython.py +0 -0
  61. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_iterables.py +0 -0
  62. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_jupyter.py +0 -0
  63. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_libcst.py +0 -0
  64. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_lightweight_charts.py +0 -0
  65. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_logging.py +0 -0
  66. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_loguru.py +0 -0
  67. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_luigi.py +0 -0
  68. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_math.py +0 -0
  69. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_memory_profiler.py +0 -0
  70. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_modules.py +0 -0
  71. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_more_itertools.py +0 -0
  72. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_numpy.py +0 -0
  73. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_operator.py +0 -0
  74. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_optuna.py +0 -0
  75. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_orjson.py +0 -0
  76. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_os.py +0 -0
  77. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_parse.py +0 -0
  78. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pathlib.py +0 -0
  79. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_period.py +0 -0
  80. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pickle.py +0 -0
  81. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_platform.py +0 -0
  82. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_polars.py +0 -0
  83. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_polars_ols.py +0 -0
  84. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pqdm.py +0 -0
  85. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_psutil.py +0 -0
  86. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pydantic.py +0 -0
  87. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pyinstrument.py +0 -0
  88. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pyrsistent.py +0 -0
  89. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pytest.py +0 -0
  90. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_pytest_regressions.py +0 -0
  91. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_python_dotenv.py +0 -0
  92. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_random.py +0 -0
  93. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_re.py +0 -0
  94. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_reprlib.py +0 -0
  95. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_rich.py +0 -0
  96. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_scipy.py +0 -0
  97. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_sentinel.py +0 -0
  98. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_shelve.py +0 -0
  99. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_slack_sdk.py +0 -0
  100. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_socket.py +0 -0
  101. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_sqlalchemy.py +0 -0
  102. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_sqlalchemy_polars.py +0 -0
  103. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_statsmodel.py +0 -0
  104. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_streamlit.py +0 -0
  105. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_string.py +0 -0
  106. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_sys.py +0 -0
  107. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_tempfile.py +0 -0
  108. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_tenacity.py +0 -0
  109. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_text.py +0 -0
  110. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_threading.py +0 -0
  111. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_timer.py +0 -0
  112. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback.py +0 -0
  113. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/__init__.py +0 -0
  114. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/chain.py +0 -0
  115. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  116. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  117. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  118. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/many.py +0 -0
  119. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/one.py +0 -0
  120. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/recursive.py +0 -0
  121. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  122. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  123. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/two.py +0 -0
  124. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_traceback_funcs/untraced.py +0 -0
  125. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_types.py +0 -0
  126. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_typing.py +0 -0
  127. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_typing_funcs/__init__.py +0 -0
  128. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_typing_funcs/no_future.py +0 -0
  129. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_typing_funcs/with_future.py +0 -0
  130. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_tzdata.py +0 -0
  131. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_tzlocal.py +0 -0
  132. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_uuid.py +0 -0
  133. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_version.py +0 -0
  134. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_warnings.py +0 -0
  135. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_whenever.py +0 -0
  136. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_zipfile.py +0 -0
  137. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/tests/test_zoneinfo.py +0 -0
  138. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/altair.py +0 -0
  139. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/asyncio.py +0 -0
  140. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/atomicwrites.py +0 -0
  141. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/atools.py +0 -0
  142. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/cachetools.py +0 -0
  143. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/click.py +0 -0
  144. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/concurrent.py +0 -0
  145. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/contextlib.py +0 -0
  146. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/contextvars.py +0 -0
  147. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/cryptography.py +0 -0
  148. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/cvxpy.py +0 -0
  149. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/dataclasses.py +0 -0
  150. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/datetime.py +0 -0
  151. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/enum.py +0 -0
  152. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/errors.py +0 -0
  153. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/eventkit.py +0 -0
  154. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/fastapi.py +0 -0
  155. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/fpdf2.py +0 -0
  156. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/functions.py +0 -0
  157. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/functools.py +0 -0
  158. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/getpass.py +0 -0
  159. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/git.py +0 -0
  160. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/hashlib.py +0 -0
  161. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/http.py +0 -0
  162. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/hypothesis.py +0 -0
  163. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/importlib.py +0 -0
  164. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/ipython.py +0 -0
  165. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/iterables.py +0 -0
  166. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/jupyter.py +0 -0
  167. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/libcst.py +0 -0
  168. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/lightweight_charts.py +0 -0
  169. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/logging.py +0 -0
  170. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/loguru.py +0 -0
  171. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/luigi.py +0 -0
  172. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/math.py +0 -0
  173. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/memory_profiler.py +0 -0
  174. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/modules.py +0 -0
  175. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/more_itertools.py +0 -0
  176. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/numpy.py +0 -0
  177. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/operator.py +0 -0
  178. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/optuna.py +0 -0
  179. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/orjson.py +0 -0
  180. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/os.py +0 -0
  181. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/parse.py +0 -0
  182. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pathlib.py +0 -0
  183. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/period.py +0 -0
  184. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pickle.py +0 -0
  185. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/platform.py +0 -0
  186. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/polars.py +0 -0
  187. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/polars_ols.py +0 -0
  188. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pqdm.py +0 -0
  189. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/psutil.py +0 -0
  190. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/py.typed +0 -0
  191. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pydantic.py +0 -0
  192. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pyinstrument.py +0 -0
  193. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pyrsistent.py +0 -0
  194. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pytest.py +0 -0
  195. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/pytest_regressions.py +0 -0
  196. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/python_dotenv.py +0 -0
  197. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/random.py +0 -0
  198. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/re.py +0 -0
  199. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/reprlib.py +0 -0
  200. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/rich.py +0 -0
  201. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/scipy.py +0 -0
  202. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/sentinel.py +0 -0
  203. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/shelve.py +0 -0
  204. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/slack_sdk.py +0 -0
  205. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/socket.py +0 -0
  206. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/sqlalchemy.py +0 -0
  207. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/sqlalchemy_polars.py +0 -0
  208. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/statsmodels.py +0 -0
  209. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/streamlit.py +0 -0
  210. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/string.py +0 -0
  211. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/sys.py +0 -0
  212. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/tempfile.py +0 -0
  213. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/tenacity.py +0 -0
  214. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/text.py +0 -0
  215. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/threading.py +0 -0
  216. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/timer.py +0 -0
  217. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/traceback.py +0 -0
  218. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/types.py +0 -0
  219. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/typing.py +0 -0
  220. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/tzdata.py +0 -0
  221. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/tzlocal.py +0 -0
  222. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/uuid.py +0 -0
  223. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/version.py +0 -0
  224. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/warnings.py +0 -0
  225. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/whenever.py +0 -0
  226. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/zipfile.py +0 -0
  227. {dycw_utilities-0.125.26 → dycw_utilities-0.126.1}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.125.26
3
+ Version: 0.126.1
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -94,7 +94,7 @@ dependencies = [
94
94
  name = "dycw-utilities"
95
95
  readme = "README.md"
96
96
  requires-python = ">= 3.12"
97
- version = "0.125.26"
97
+ version = "0.126.1"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
@@ -335,7 +335,7 @@ zzz-test-zoneinfo = [
335
335
  # bump-my-version
336
336
  [tool.bumpversion]
337
337
  allow_dirty = true
338
- current_version = "0.125.26"
338
+ current_version = "0.126.1"
339
339
 
340
340
  [[tool.bumpversion.files]]
341
341
  filename = "src/utilities/__init__.py"
@@ -52,7 +52,6 @@ from utilities.dataclasses import replace_non_sentinel
52
52
  from utilities.datetime import (
53
53
  MILLISECOND,
54
54
  MINUTE,
55
- SECOND,
56
55
  datetime_duration_to_timedelta,
57
56
  get_now,
58
57
  )
@@ -964,7 +963,7 @@ class TestLooper:
964
963
  async def test_context_manager_already_entered(
965
964
  self, *, caplog: LogCaptureFixture
966
965
  ) -> None:
967
- looper = _ExampleCounterLooper(auto_start=True, timeout=SECOND)
966
+ looper = _ExampleCounterLooper(timeout=1.0)
968
967
  async with looper, looper:
969
968
  ...
970
969
  _ = one(m for m in caplog.messages if search(": already entered$", m))
@@ -1,25 +1,31 @@
1
1
  from __future__ import annotations
2
2
 
3
- from asyncio import create_task, get_running_loop, sleep
4
- from io import BytesIO, StringIO
3
+ from asyncio import Queue, sleep
4
+ from os import getpid
5
+ from re import search
5
6
  from typing import TYPE_CHECKING, Any
6
7
 
7
8
  from hypothesis import HealthCheck, Phase, given, settings
8
9
  from hypothesis.strategies import (
9
10
  DataObject,
11
+ DrawFn,
12
+ binary,
10
13
  booleans,
14
+ composite,
11
15
  data,
12
16
  dictionaries,
13
17
  lists,
14
18
  sampled_from,
19
+ uuids,
15
20
  )
16
- from pytest import mark, raises
21
+ from pytest import LogCaptureFixture, mark, param, raises
17
22
  from redis.asyncio import Redis
23
+ from redis.asyncio.client import PubSub
18
24
 
19
25
  from tests.conftest import SKIPIF_CI_AND_NOT_LINUX
20
26
  from tests.test_operator import make_objects
21
- from utilities.asyncio import EnhancedTaskGroup
22
- from utilities.functions import get_class_name
27
+ from utilities.asyncio import get_items_nowait
28
+ from utilities.datetime import serialize_compact
23
29
  from utilities.hypothesis import (
24
30
  int64s,
25
31
  pairs,
@@ -27,174 +33,178 @@ from utilities.hypothesis import (
27
33
  text_ascii,
28
34
  yield_test_redis,
29
35
  )
36
+ from utilities.iterables import one
37
+ from utilities.operator import is_equal
30
38
  from utilities.orjson import deserialize, serialize
31
39
  from utilities.redis import (
32
40
  Publisher,
33
41
  PublisherError,
42
+ PublishError,
43
+ PublishService,
44
+ SubscribeService,
45
+ _is_message,
46
+ _RedisMessage,
34
47
  publish,
35
48
  redis_hash_map_key,
36
49
  redis_key,
37
50
  subscribe,
38
- subscribe_messages,
51
+ yield_pubsub,
39
52
  yield_redis,
40
53
  )
41
54
  from utilities.sentinel import SENTINEL_REPR, Sentinel, sentinel
55
+ from utilities.tzlocal import get_now_local
42
56
 
43
57
  if TYPE_CHECKING:
44
- from collections.abc import Mapping
45
-
46
- from pytest import CaptureFixture
47
- from redis.asyncio.client import PubSub
48
-
49
-
50
- class TestPublishAndSubscribe:
51
- @given(
52
- data=data(),
53
- channel=text_ascii(min_size=1).map(
54
- lambda c: f"{get_class_name(TestPublishAndSubscribe)}_all_objects_with_serialize_{c}"
55
- ),
56
- obj=make_objects(),
57
- )
58
- @mark.flaky
59
- @settings(
60
- max_examples=1,
61
- phases={Phase.generate},
62
- suppress_health_check={HealthCheck.function_scoped_fixture},
58
+ from collections.abc import Mapping, Sequence
59
+ from pathlib import Path
60
+
61
+
62
+ _PUB_SUB_SLEEP = 0.1
63
+
64
+
65
+ @composite
66
+ def channels(draw: DrawFn, /) -> str:
67
+ now = serialize_compact(get_now_local())
68
+ key = draw(uuids())
69
+ pid = getpid()
70
+ return f"test_{now}_{key}_{pid}"
71
+
72
+
73
+ class TestIsMessage:
74
+ @mark.parametrize(
75
+ ("message", "channels", "expected"),
76
+ [
77
+ param(
78
+ {
79
+ "type": "message",
80
+ "pattern": None,
81
+ "channel": b"channel",
82
+ "data": b"data",
83
+ },
84
+ [b"channel"],
85
+ True,
86
+ ),
87
+ param(None, [], False),
88
+ param({"type": "invalid"}, [], False),
89
+ param({"type": "message"}, [], False),
90
+ param({"type": "message", "pattern": False}, [], False),
91
+ param({"type": "message", "pattern": None}, [], False),
92
+ param(
93
+ {"type": "message", "pattern": None, "channel": b"channel1"},
94
+ [b"channel2"],
95
+ False,
96
+ ),
97
+ param(
98
+ {"type": "message", "pattern": None, "channel": b"channel"},
99
+ [b"channel"],
100
+ False,
101
+ ),
102
+ param(
103
+ {
104
+ "type": "message",
105
+ "pattern": None,
106
+ "channel": b"channel",
107
+ "data": None,
108
+ },
109
+ [b"channel"],
110
+ False,
111
+ ),
112
+ ],
63
113
  )
64
- @SKIPIF_CI_AND_NOT_LINUX
65
- async def test_all_objects_with_serialize(
66
- self, *, capsys: CaptureFixture, data: DataObject, channel: str, obj: Any
114
+ def test_main(
115
+ self, *, message: Any, channels: Sequence[bytes], expected: bool
67
116
  ) -> None:
68
- async with yield_test_redis(data) as test:
69
-
70
- async def listener() -> None:
71
- async for msg in subscribe(
72
- test.redis.pubsub(), channel, deserializer=deserialize
73
- ):
74
- print(msg) # noqa: T201
117
+ result = _is_message(message, channels=channels)
118
+ assert result is expected
75
119
 
76
- task = create_task(listener())
77
- await sleep(0.05)
78
- _ = await publish(test.redis, channel, obj, serializer=serialize)
79
- await sleep(0.05)
80
- try:
81
- out = capsys.readouterr().out
82
- expected = f"{obj}\n"
83
- assert out == expected
84
- finally:
85
- _ = task.cancel()
86
120
 
87
- @given(
88
- data=data(),
89
- channel=text_ascii(min_size=1).map(
90
- lambda c: f"{get_class_name(TestPublishAndSubscribe)}_text_without_serialize_{c}"
91
- ),
92
- text=text_ascii(min_size=1),
93
- )
94
- @settings(
95
- max_examples=1,
96
- phases={Phase.generate},
97
- suppress_health_check={HealthCheck.function_scoped_fixture},
98
- )
121
+ class TestPublish:
122
+ @given(channel=channels(), data=lists(binary(min_size=1), min_size=1, max_size=5))
123
+ @settings_with_reduced_examples(phases={Phase.generate})
99
124
  @SKIPIF_CI_AND_NOT_LINUX
100
- async def test_text_without_serialize(
101
- self, *, capsys: CaptureFixture, data: DataObject, channel: str, text: str
102
- ) -> None:
103
- async with yield_test_redis(data) as test:
104
-
105
- async def listener() -> None:
106
- async for msg in subscribe(test.redis.pubsub(), channel):
107
- print(msg) # noqa: T201
108
-
109
- task = create_task(listener())
110
- await sleep(0.05)
111
- _ = await publish(test.redis, channel, text)
112
- await sleep(0.05)
113
- try:
114
- out = capsys.readouterr().out
115
- expected = f"{text.encode()}\n"
116
- assert out == expected
117
- finally:
118
- _ = task.cancel()
119
-
120
-
121
- class TestPublisher:
122
- @given(
123
- data=data(),
124
- channel=text_ascii(min_size=1).map(
125
- lambda c: f"{get_class_name(TestPublisher)}_main_{c}"
126
- ),
127
- obj=make_objects(),
128
- )
129
- @mark.flaky
130
- @settings(
131
- max_examples=1,
132
- phases={Phase.generate},
133
- suppress_health_check={HealthCheck.function_scoped_fixture},
134
- )
125
+ async def test_bytes(self, *, data: Sequence[bytes], channel: str) -> None:
126
+ queue: Queue[bytes] = Queue()
127
+ async with (
128
+ yield_redis() as redis,
129
+ subscribe(redis, channel, queue, output="bytes"),
130
+ ):
131
+ await sleep(_PUB_SUB_SLEEP)
132
+ for datum in data:
133
+ _ = await publish(redis, channel, datum)
134
+ await sleep(_PUB_SUB_SLEEP) # keep in context
135
+ assert queue.qsize() == len(data)
136
+ results = get_items_nowait(queue)
137
+ for result, datum in zip(results, data, strict=True):
138
+ assert isinstance(result, bytes)
139
+ assert result == datum
140
+
141
+ @given(channel=channels(), objects=lists(make_objects(), min_size=1, max_size=5))
142
+ @settings_with_reduced_examples(phases={Phase.generate})
135
143
  @SKIPIF_CI_AND_NOT_LINUX
136
- async def test_main(self, *, data: DataObject, channel: str, obj: Any) -> None:
137
- buffer = StringIO()
144
+ async def test_serializer(self, *, channel: str, objects: Sequence[Any]) -> None:
145
+ queue: Queue[Any] = Queue()
138
146
  async with (
139
- yield_test_redis(data) as test,
140
- Publisher(
141
- duration=1.0, redis=test.redis, serializer=serialize, sleep_core=0.1
142
- ) as publisher,
147
+ yield_redis() as redis,
148
+ subscribe(redis, channel, queue, output=deserialize),
143
149
  ):
150
+ await sleep(_PUB_SUB_SLEEP)
151
+ for obj in objects:
152
+ _ = await publish(redis, channel, obj, serializer=serialize)
153
+ await sleep(_PUB_SUB_SLEEP) # keep in context
154
+ assert queue.qsize() == len(objects)
155
+ results = get_items_nowait(queue)
156
+ for result, obj in zip(results, objects, strict=True):
157
+ assert is_equal(result, obj)
144
158
 
145
- async def listener() -> None:
146
- async for obj_i in subscribe(
147
- test.redis.pubsub(), channel, deserializer=deserialize
148
- ):
149
- _ = buffer.write(str(obj_i))
150
-
151
- async def sleep_then_put() -> None:
152
- await sleep(0.1)
153
- publisher.put_right_nowait((channel, obj))
154
-
155
- with raises(ExceptionGroup): # noqa: PT012
156
- async with EnhancedTaskGroup(timeout=1.0) as tg:
157
- _ = tg.create_task(listener())
158
- _ = tg.create_task(sleep_then_put())
159
+ @given(
160
+ channel=channels(),
161
+ messages=lists(text_ascii(min_size=1), min_size=1, max_size=5),
162
+ )
163
+ @settings_with_reduced_examples(phases={Phase.generate})
164
+ @SKIPIF_CI_AND_NOT_LINUX
165
+ async def test_text(self, *, channel: str, messages: Sequence[str]) -> None:
166
+ queue: Queue[str] = Queue()
167
+ async with yield_redis() as redis, subscribe(redis, channel, queue):
168
+ await sleep(_PUB_SUB_SLEEP)
169
+ for message in messages:
170
+ _ = await publish(redis, channel, message)
171
+ await sleep(_PUB_SUB_SLEEP) # keep in context
172
+ assert queue.qsize() == len(messages)
173
+ results = get_items_nowait(queue)
174
+ for result, message in zip(results, messages, strict=True):
175
+ assert isinstance(result, str)
176
+ assert result == message
177
+
178
+ async def test_error(self) -> None:
179
+ async with yield_redis() as redis:
180
+ with raises(
181
+ PublishError, match="Unable to publish data None with serializer None"
182
+ ):
183
+ _ = await publish(redis, "channel", None)
159
184
 
160
- assert buffer.getvalue() == str(obj)
161
185
 
186
+ class TestPublisher:
162
187
  @given(
163
- data=data(),
164
- channel=text_ascii(min_size=1).map(
165
- lambda c: f"{get_class_name(TestPublisher)}_text_without_serialize_{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},
188
+ channel=channels(),
189
+ messages=lists(text_ascii(min_size=1), min_size=1, max_size=5),
173
190
  )
191
+ @settings_with_reduced_examples(phases={Phase.generate})
174
192
  @SKIPIF_CI_AND_NOT_LINUX
175
- async def test_text_without_serialize(
176
- self, *, data: DataObject, channel: str, text: str
177
- ) -> None:
178
- buffer = BytesIO()
193
+ async def test_main(self, *, channel: str, messages: Sequence[str]) -> None:
194
+ queue: Queue[str] = Queue()
179
195
  async with (
180
- yield_test_redis(data) as test,
181
- Publisher(duration=1.0, redis=test.redis, sleep_core=0.1) as publisher,
196
+ yield_redis() as redis,
197
+ Publisher(duration=1.0, redis=redis, sleep_core=0.1) as publisher,
198
+ subscribe(redis, channel, queue),
182
199
  ):
183
-
184
- async def listener() -> None:
185
- async for bytes_i in subscribe(test.redis.pubsub(), channel):
186
- _ = buffer.write(bytes_i)
187
-
188
- async def sleep_then_put() -> None:
189
- await sleep(0.1)
190
- publisher.put_right_nowait((channel, text))
191
-
192
- with raises(ExceptionGroup): # noqa: PT012
193
- async with EnhancedTaskGroup(timeout=1.0) as tg:
194
- _ = tg.create_task(listener())
195
- _ = tg.create_task(sleep_then_put())
196
-
197
- assert buffer.getvalue() == text.encode()
200
+ await sleep(_PUB_SUB_SLEEP)
201
+ publisher.put_right_nowait(*((channel, m) for m in messages))
202
+ await sleep(_PUB_SUB_SLEEP) # keep in context
203
+ assert queue.qsize() == len(messages)
204
+ results = get_items_nowait(queue)
205
+ for result, message in zip(results, messages, strict=True):
206
+ assert isinstance(result, str)
207
+ assert result == message
198
208
 
199
209
  @given(data=data())
200
210
  @SKIPIF_CI_AND_NOT_LINUX
@@ -204,67 +214,27 @@ class TestPublisher:
204
214
  with raises(PublisherError, match="Error running 'Publisher'"):
205
215
  raise PublisherError(publisher=publisher)
206
216
 
207
-
208
- class TestSubscribeMessages:
209
217
  @given(
210
- channel=text_ascii(min_size=1).map(
211
- lambda c: f"{get_class_name(TestSubscribeMessages)}_redis_{c}"
212
- ),
213
- message=text_ascii(min_size=1),
214
- )
215
- @settings(
216
- max_examples=1,
217
- phases={Phase.generate},
218
- suppress_health_check={HealthCheck.function_scoped_fixture},
219
- )
220
- @SKIPIF_CI_AND_NOT_LINUX
221
- async def test_redis(
222
- self, *, capsys: CaptureFixture, channel: str, message: str
223
- ) -> None:
224
- redis = Redis()
225
- await self._run_test(redis, redis, capsys, channel, message)
226
-
227
- @given(
228
- channel=text_ascii(min_size=1).map(
229
- lambda c: f"{get_class_name(TestSubscribeMessages)}_pubsub_{c}"
230
- ),
231
- message=text_ascii(min_size=1),
232
- )
233
- @settings(
234
- max_examples=1,
235
- phases={Phase.generate},
236
- suppress_health_check={HealthCheck.function_scoped_fixture},
218
+ channel=channels(),
219
+ messages=lists(text_ascii(min_size=1), min_size=1, max_size=5),
237
220
  )
221
+ @settings_with_reduced_examples(phases={Phase.generate})
238
222
  @SKIPIF_CI_AND_NOT_LINUX
239
- async def test_pubsub(
240
- self, *, capsys: CaptureFixture, channel: str, message: str
241
- ) -> None:
242
- redis = Redis()
243
- await self._run_test(redis, redis.pubsub(), capsys, channel, message)
244
-
245
- async def _run_test(
246
- self,
247
- redis: Redis,
248
- redis_or_pubsub: Redis | PubSub,
249
- capsys: CaptureFixture,
250
- channel: str,
251
- message: str,
252
- /,
253
- ) -> None:
254
- async def listener() -> None:
255
- async for msg in subscribe_messages(redis_or_pubsub, channel):
256
- print(msg) # noqa: T201
257
-
258
- task = get_running_loop().create_task(listener())
259
- await sleep(0.05)
260
- _ = await redis.publish(channel, message)
261
- await sleep(0.05)
262
- try:
263
- out = capsys.readouterr().out
264
- expected = f"{{'type': 'message', 'pattern': None, 'channel': b'{channel}', 'data': b'{message}'}}\n"
265
- assert out == expected
266
- finally:
267
- _ = task.cancel()
223
+ async def test_main_service(self, *, channel: str, messages: Sequence[str]) -> None:
224
+ queue: Queue[str] = Queue()
225
+ async with (
226
+ yield_redis() as redis,
227
+ PublishService(freq=0.1, timeout=1.0, redis=redis) as service,
228
+ subscribe(redis, channel, queue),
229
+ ):
230
+ await sleep(_PUB_SUB_SLEEP)
231
+ service.put_right_nowait(*((channel, m) for m in messages))
232
+ await sleep(_PUB_SUB_SLEEP) # keep in context
233
+ assert queue.qsize() == len(messages)
234
+ results = get_items_nowait(queue)
235
+ for result, message in zip(results, messages, strict=True):
236
+ assert isinstance(result, str)
237
+ assert result == message
268
238
 
269
239
 
270
240
  class TestRedisHashMapKey:
@@ -536,7 +506,153 @@ class TestRedisKey:
536
506
  assert not await key.exists(test.redis)
537
507
 
538
508
 
509
+ class TestSubscribe:
510
+ @given(
511
+ channel=channels(), messages=lists(binary(min_size=1), min_size=1, max_size=5)
512
+ )
513
+ @settings_with_reduced_examples(phases={Phase.generate})
514
+ @SKIPIF_CI_AND_NOT_LINUX
515
+ async def test_bytes(self, *, channel: str, messages: Sequence[bytes]) -> None:
516
+ queue: Queue[bytes] = Queue()
517
+ async with (
518
+ yield_redis() as redis,
519
+ subscribe(redis, channel, queue, output="bytes"),
520
+ ):
521
+ await sleep(_PUB_SUB_SLEEP)
522
+ for message in messages:
523
+ await redis.publish(channel, message)
524
+ await sleep(_PUB_SUB_SLEEP) # keep in context
525
+ assert queue.qsize() == len(messages)
526
+ results = get_items_nowait(queue)
527
+ for result, message in zip(results, messages, strict=True):
528
+ assert isinstance(result, bytes)
529
+ assert result == message
530
+
531
+ @given(channel=channels(), objs=lists(make_objects(), min_size=1, max_size=5))
532
+ @settings_with_reduced_examples(phases={Phase.generate})
533
+ @SKIPIF_CI_AND_NOT_LINUX
534
+ async def test_deserialize(self, *, channel: str, objs: Sequence[Any]) -> None:
535
+ queue: Queue[Any] = Queue()
536
+ async with (
537
+ yield_redis() as redis,
538
+ subscribe(redis, channel, queue, output=deserialize),
539
+ ):
540
+ await sleep(_PUB_SUB_SLEEP)
541
+ for obj in objs:
542
+ await redis.publish(channel, serialize(obj))
543
+ await sleep(_PUB_SUB_SLEEP) # keep in context
544
+ assert queue.qsize() == len(objs)
545
+ results = get_items_nowait(queue)
546
+ for result, obj in zip(results, objs, strict=True):
547
+ assert is_equal(result, obj)
548
+
549
+ @given(
550
+ channel=channels(),
551
+ messages=lists(text_ascii(min_size=1), min_size=1, max_size=5),
552
+ )
553
+ @settings_with_reduced_examples(phases={Phase.generate})
554
+ @SKIPIF_CI_AND_NOT_LINUX
555
+ async def test_raw(self, *, channel: str, messages: Sequence[str]) -> None:
556
+ queue: Queue[_RedisMessage] = Queue()
557
+ async with (
558
+ yield_redis() as redis,
559
+ subscribe(redis, channel, queue, output="raw"),
560
+ ):
561
+ await sleep(_PUB_SUB_SLEEP)
562
+ for message in messages:
563
+ await redis.publish(channel, message)
564
+ await sleep(_PUB_SUB_SLEEP) # keep in context
565
+ assert queue.qsize() == len(messages)
566
+ results = get_items_nowait(queue)
567
+ for result, message in zip(results, messages, strict=True):
568
+ assert isinstance(result, dict)
569
+ assert result["type"] == "message"
570
+ assert result["pattern"] is None
571
+ assert result["channel"] == channel.encode()
572
+ assert result["data"] == message.encode()
573
+
574
+ @given(
575
+ channel=channels(),
576
+ messages=lists(text_ascii(min_size=1), min_size=1, max_size=5),
577
+ )
578
+ @settings_with_reduced_examples(phases={Phase.generate})
579
+ @SKIPIF_CI_AND_NOT_LINUX
580
+ async def test_text(self, *, channel: str, messages: Sequence[str]) -> None:
581
+ queue: Queue[_RedisMessage] = Queue()
582
+ async with (
583
+ yield_redis() as redis,
584
+ subscribe(redis, channel, queue, output="raw"),
585
+ ):
586
+ await sleep(_PUB_SUB_SLEEP)
587
+ for message in messages:
588
+ await redis.publish(channel, message)
589
+ await sleep(_PUB_SUB_SLEEP) # keep in context
590
+ assert queue.qsize() == len(messages)
591
+ results = get_items_nowait(queue)
592
+ for result, message in zip(results, messages, strict=True):
593
+ assert isinstance(result, dict)
594
+ assert result["type"] == "message"
595
+ assert result["pattern"] is None
596
+ assert result["channel"] == channel.encode()
597
+ assert result["data"] == message.encode()
598
+
599
+
600
+ class TestSubscribeService:
601
+ @given(
602
+ channel=channels(),
603
+ messages=lists(text_ascii(min_size=1), min_size=1, max_size=5),
604
+ )
605
+ @settings_with_reduced_examples(phases={Phase.generate})
606
+ @SKIPIF_CI_AND_NOT_LINUX
607
+ async def test_main(self, *, channel: str, messages: list[str]) -> None:
608
+ async with (
609
+ yield_redis() as redis,
610
+ SubscribeService(timeout=1.0, redis=redis, channel=channel) as service,
611
+ ):
612
+ await sleep(_PUB_SUB_SLEEP)
613
+ for message in messages:
614
+ await redis.publish(channel, message)
615
+ await sleep(_PUB_SUB_SLEEP) # keep in context
616
+ assert service.qsize() == len(messages)
617
+ results = service.get_all_nowait()
618
+ for result, message in zip(results, messages, strict=True):
619
+ assert isinstance(result, str)
620
+ assert result == message
621
+
622
+ @given(channel=channels())
623
+ @settings(
624
+ max_examples=1,
625
+ phases={Phase.generate},
626
+ suppress_health_check={HealthCheck.function_scoped_fixture},
627
+ )
628
+ @SKIPIF_CI_AND_NOT_LINUX
629
+ async def test_context_manager_already_subscribing(
630
+ self, *, channel: str, caplog: LogCaptureFixture
631
+ ) -> None:
632
+ async with yield_redis() as redis:
633
+ looper = SubscribeService(
634
+ timeout=1.0, _debug=True, redis=redis, channel=channel
635
+ )
636
+ async with looper, looper:
637
+ ...
638
+ _ = one(m for m in caplog.messages if search(": already subscribing$", m))
639
+ _ = one(
640
+ m
641
+ for m in caplog.messages
642
+ if search(": already stopped subscription$", m)
643
+ )
644
+
645
+
539
646
  class TestYieldClient:
540
- async def test_sync(self) -> None:
647
+ @SKIPIF_CI_AND_NOT_LINUX
648
+ async def test_main(self) -> None:
541
649
  async with yield_redis() as client:
542
650
  assert isinstance(client, Redis)
651
+
652
+
653
+ class TestYieldPubSub:
654
+ @SKIPIF_CI_AND_NOT_LINUX
655
+ async def test_main(self, *, tmp_path: Path) -> None:
656
+ channel = str(tmp_path)
657
+ async with yield_redis() as redis, yield_pubsub(redis, channel) as pubsub:
658
+ assert isinstance(pubsub, PubSub)
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.125.26"
3
+ __version__ = "0.126.1"