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