dycw-utilities 0.130.2__tar.gz → 0.131.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 (221) hide show
  1. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/PKG-INFO +1 -1
  2. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/pyproject.toml +3 -2
  3. dycw_utilities-0.131.1/src/tests/conftest.py +425 -0
  4. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_hypothesis.py +0 -39
  5. dycw_utilities-0.131.1/src/tests/test_pottery.py +92 -0
  6. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_sqlalchemy.py +283 -258
  7. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_sqlalchemy_polars.py +125 -98
  8. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/__init__.py +1 -1
  9. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/hypothesis.py +0 -48
  10. dycw_utilities-0.131.1/src/utilities/pottery.py +116 -0
  11. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/sqlalchemy_polars.py +1 -17
  12. dycw_utilities-0.130.2/src/tests/conftest.py +0 -27
  13. dycw_utilities-0.130.2/src/tests/test_pottery.py +0 -28
  14. dycw_utilities-0.130.2/src/utilities/pottery.py +0 -50
  15. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/.gitignore +0 -0
  16. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/LICENSE +0 -0
  17. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/README.md +0 -0
  18. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/__init__.py +0 -0
  19. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/__init__.py +0 -0
  20. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_missing/__init__.py +0 -0
  21. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_missing/module.py +0 -0
  22. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/__init__.py +0 -0
  23. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/outer_1.py +0 -0
  24. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/outer_2.py +0 -0
  25. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  26. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  27. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  28. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  29. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_without/__init__.py +0 -0
  30. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_without/module_1.py +0 -0
  31. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/package_without/module_2.py +0 -0
  32. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/standalone.py +0 -0
  33. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/modules/with_imports.py +0 -0
  34. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  35. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  36. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  37. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  38. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  39. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  40. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  41. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  42. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_aiolimiter.py +0 -0
  43. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_altair.py +0 -0
  44. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_asyncio.py +0 -0
  45. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_asyncio_classes/__init__.py +0 -0
  46. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_asyncio_classes/loopers.py +0 -0
  47. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_asyncio_classes/redis.py +0 -0
  48. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_atomicwrites.py +0 -0
  49. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_atools.py +0 -0
  50. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_cachetools.py +0 -0
  51. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_click.py +0 -0
  52. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_concurrent.py +0 -0
  53. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_contextlib.py +0 -0
  54. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_contextvars.py +0 -0
  55. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_cryptography.py +0 -0
  56. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_cvxpy.py +0 -0
  57. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_dataclasses.py +0 -0
  58. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_datetime.py +0 -0
  59. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_enum.py +0 -0
  60. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_errors.py +0 -0
  61. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_eventkit.py +0 -0
  62. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_fastapi.py +0 -0
  63. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_fpdf2.py +0 -0
  64. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_functions.py +0 -0
  65. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_functools.py +0 -0
  66. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_getpass.py +0 -0
  67. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_git.py +0 -0
  68. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_hashlib.py +0 -0
  69. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_http.py +0 -0
  70. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_importlib.py +0 -0
  71. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_inflect.py +0 -0
  72. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_ipython.py +0 -0
  73. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_iterables.py +0 -0
  74. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_jupyter.py +0 -0
  75. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_libcst.py +0 -0
  76. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_lightweight_charts.py +0 -0
  77. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_logging.py +0 -0
  78. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_luigi.py +0 -0
  79. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_math.py +0 -0
  80. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_memory_profiler.py +0 -0
  81. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_modules.py +0 -0
  82. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_more_itertools.py +0 -0
  83. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_numpy.py +0 -0
  84. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_operator.py +0 -0
  85. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_optuna.py +0 -0
  86. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_orjson.py +0 -0
  87. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_os.py +0 -0
  88. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_parse.py +0 -0
  89. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pathlib.py +0 -0
  90. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_period.py +0 -0
  91. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pickle.py +0 -0
  92. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_platform.py +0 -0
  93. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_polars.py +0 -0
  94. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_polars_ols.py +0 -0
  95. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pqdm.py +0 -0
  96. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_psutil.py +0 -0
  97. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pydantic.py +0 -0
  98. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pyinstrument.py +0 -0
  99. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pyrsistent.py +0 -0
  100. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pytest.py +0 -0
  101. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_pytest_regressions.py +0 -0
  102. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_python_dotenv.py +0 -0
  103. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_random.py +0 -0
  104. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_re.py +0 -0
  105. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_redis.py +0 -0
  106. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_reprlib.py +0 -0
  107. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_scipy.py +0 -0
  108. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_sentinel.py +0 -0
  109. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_shelve.py +0 -0
  110. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_slack_sdk.py +0 -0
  111. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_socket.py +0 -0
  112. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_statsmodel.py +0 -0
  113. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_streamlit.py +0 -0
  114. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_string.py +0 -0
  115. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_tempfile.py +0 -0
  116. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_tenacity.py +0 -0
  117. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_text.py +0 -0
  118. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_threading.py +0 -0
  119. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_timer.py +0 -0
  120. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_traceback.py +0 -0
  121. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_types.py +0 -0
  122. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_typing.py +0 -0
  123. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_typing_funcs/__init__.py +0 -0
  124. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_typing_funcs/no_future.py +0 -0
  125. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_typing_funcs/with_future.py +0 -0
  126. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_tzdata.py +0 -0
  127. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_tzlocal.py +0 -0
  128. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_uuid.py +0 -0
  129. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_version.py +0 -0
  130. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_warnings.py +0 -0
  131. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_whenever.py +0 -0
  132. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_zipfile.py +0 -0
  133. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/tests/test_zoneinfo.py +0 -0
  134. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/aiolimiter.py +0 -0
  135. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/altair.py +0 -0
  136. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/asyncio.py +0 -0
  137. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/atomicwrites.py +0 -0
  138. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/atools.py +0 -0
  139. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/cachetools.py +0 -0
  140. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/click.py +0 -0
  141. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/concurrent.py +0 -0
  142. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/contextlib.py +0 -0
  143. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/contextvars.py +0 -0
  144. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/cryptography.py +0 -0
  145. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/cvxpy.py +0 -0
  146. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/dataclasses.py +0 -0
  147. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/datetime.py +0 -0
  148. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/enum.py +0 -0
  149. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/errors.py +0 -0
  150. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/eventkit.py +0 -0
  151. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/fastapi.py +0 -0
  152. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/fpdf2.py +0 -0
  153. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/functions.py +0 -0
  154. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/functools.py +0 -0
  155. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/getpass.py +0 -0
  156. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/git.py +0 -0
  157. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/hashlib.py +0 -0
  158. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/http.py +0 -0
  159. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/importlib.py +0 -0
  160. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/inflect.py +0 -0
  161. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/ipython.py +0 -0
  162. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/iterables.py +0 -0
  163. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/jupyter.py +0 -0
  164. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/libcst.py +0 -0
  165. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/lightweight_charts.py +0 -0
  166. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/logging.py +0 -0
  167. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/luigi.py +0 -0
  168. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/math.py +0 -0
  169. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/memory_profiler.py +0 -0
  170. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/modules.py +0 -0
  171. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/more_itertools.py +0 -0
  172. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/numpy.py +0 -0
  173. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/operator.py +0 -0
  174. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/optuna.py +0 -0
  175. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/orjson.py +0 -0
  176. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/os.py +0 -0
  177. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/parse.py +0 -0
  178. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pathlib.py +0 -0
  179. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/period.py +0 -0
  180. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pickle.py +0 -0
  181. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/platform.py +0 -0
  182. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/polars.py +0 -0
  183. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/polars_ols.py +0 -0
  184. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pqdm.py +0 -0
  185. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/psutil.py +0 -0
  186. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/py.typed +0 -0
  187. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pydantic.py +0 -0
  188. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pyinstrument.py +0 -0
  189. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pyrsistent.py +0 -0
  190. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pytest.py +0 -0
  191. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/pytest_regressions.py +0 -0
  192. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/python_dotenv.py +0 -0
  193. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/random.py +0 -0
  194. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/re.py +0 -0
  195. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/redis.py +0 -0
  196. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/reprlib.py +0 -0
  197. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/scipy.py +0 -0
  198. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/sentinel.py +0 -0
  199. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/shelve.py +0 -0
  200. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/slack_sdk.py +0 -0
  201. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/socket.py +0 -0
  202. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/sqlalchemy.py +0 -0
  203. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/statsmodels.py +0 -0
  204. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/streamlit.py +0 -0
  205. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/string.py +0 -0
  206. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/tempfile.py +0 -0
  207. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/tenacity.py +0 -0
  208. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/text.py +0 -0
  209. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/threading.py +0 -0
  210. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/timer.py +0 -0
  211. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/traceback.py +0 -0
  212. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/types.py +0 -0
  213. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/typing.py +0 -0
  214. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/tzdata.py +0 -0
  215. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/tzlocal.py +0 -0
  216. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/uuid.py +0 -0
  217. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/version.py +0 -0
  218. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/warnings.py +0 -0
  219. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/whenever.py +0 -0
  220. {dycw_utilities-0.130.2 → dycw_utilities-0.131.1}/src/utilities/zipfile.py +0 -0
  221. {dycw_utilities-0.130.2 → dycw_utilities-0.131.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.130.2
3
+ Version: 0.131.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.130.2"
97
+ version = "0.131.1"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  logging = [
@@ -341,7 +341,7 @@ zzz-test-zoneinfo = [
341
341
  # bump-my-version
342
342
  [tool.bumpversion]
343
343
  allow_dirty = true
344
- current_version = "0.130.2"
344
+ current_version = "0.131.1"
345
345
 
346
346
  [[tool.bumpversion.files]]
347
347
  filename = "src/utilities/__init__.py"
@@ -460,6 +460,7 @@ filterwarnings = [
460
460
  "ignore:unclosed <StreamWriter .*>:ResourceWarning", # redis
461
461
  "ignore:unclosed <socket.*socket .*>:ResourceWarning", # redis
462
462
  "ignore:unclosed Connection <redis.*asyncio.*connection.*Connection.*>:ResourceWarning", # redis
463
+ "ignore:unclosed connection <asyncpg.*connection.*Connection.*>:ResourceWarning", # asyncpg
463
464
  "ignore:unclosed event loop <_UnixSelectorEventLoop .*>:ResourceWarning", # redis
464
465
  "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging
465
466
  "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis
@@ -0,0 +1,425 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from asyncio import sleep
5
+ from contextlib import suppress
6
+ from os import environ
7
+ from re import MULTILINE, Pattern
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from pytest import fixture, mark, param
11
+ from sqlalchemy import text
12
+
13
+ from utilities.datetime import MINUTE, get_now, parse_datetime_compact
14
+ from utilities.platform import IS_NOT_LINUX, IS_WINDOWS
15
+ from utilities.re import ExtractGroupError, extract_group
16
+ from utilities.text import strip_and_dedent
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Sequence
20
+ from pathlib import Path
21
+
22
+ from _pytest.fixtures import SubRequest
23
+
24
+
25
+ FLAKY = mark.flaky(reruns=5, reruns_delay=1)
26
+ IS_CI = "CI" in environ
27
+ SKIPIF_CI = mark.skipif(IS_CI, reason="Skipped for CI")
28
+ IS_CI_AND_WINDOWS = IS_CI and IS_WINDOWS
29
+ SKIPIF_CI_AND_WINDOWS = mark.skipif(IS_CI_AND_WINDOWS, reason="Skipped for CI/Windows")
30
+ SKIPIF_CI_AND_NOT_LINUX = mark.skipif(
31
+ IS_CI and IS_NOT_LINUX, reason="Skipped for CI/non-Linux"
32
+ )
33
+
34
+
35
+ # hypothesis
36
+
37
+
38
+ try:
39
+ from utilities.hypothesis import setup_hypothesis_profiles
40
+ except ModuleNotFoundError:
41
+ pass
42
+ else:
43
+ setup_hypothesis_profiles()
44
+
45
+
46
+ # fixtures - sqlalchemy
47
+
48
+
49
+ @fixture(params=[param("sqlite"), param("postgresql", marks=SKIPIF_CI)])
50
+ async def test_engine(*, request: SubRequest, tmp_path: Path) -> Any:
51
+ from utilities.sqlalchemy import create_async_engine
52
+
53
+ dialect = request.param
54
+ match dialect:
55
+ case "sqlite":
56
+ db_path = tmp_path / "db.sqlite"
57
+ return create_async_engine("sqlite+aiosqlite", database=str(db_path))
58
+ case "postgresql":
59
+ engine = create_async_engine(
60
+ "postgresql+asyncpg", host="localhost", port=5432, database="testing"
61
+ )
62
+ query = text("SELECT tablename FROM pg_tables")
63
+ async with engine.begin() as conn:
64
+ tables: Sequence[str] = (await conn.execute(query)).scalars().all()
65
+ for table in tables:
66
+ if _is_to_drop(table):
67
+ async with engine.begin() as conn:
68
+ with suppress(Exception):
69
+ _ = await conn.execute(
70
+ text(f'DROP TABLE IF EXISTS "{table}" CASCADE')
71
+ )
72
+ await sleep(0.01)
73
+ return engine
74
+ case _:
75
+ msg = f"Unsupported dialect: {dialect}"
76
+ raise NotImplementedError(msg)
77
+
78
+
79
+ def _is_to_drop(table: str, /) -> bool:
80
+ try:
81
+ datetime_str = extract_group(r"^(\d{8}T\d{6})_", table)
82
+ except ExtractGroupError:
83
+ return True
84
+ datetime = parse_datetime_compact(datetime_str)
85
+ now = get_now()
86
+ return (now - datetime) >= MINUTE
87
+
88
+
89
+ # fixtures - traceback
90
+
91
+
92
+ @fixture
93
+ def traceback_func_chain() -> Pattern[str]:
94
+ return re.compile(
95
+ strip_and_dedent(
96
+ r"""
97
+ Date/time \| .+
98
+ Started \| .+
99
+ Duration \| .+
100
+ User \| .+
101
+ Host \| .+
102
+ Version \|\s
103
+
104
+ Exception chain 1/2:
105
+ Frame 1/1: func_chain_first \(tests\.test_traceback_funcs\.chain\)
106
+ Inputs:
107
+ args\[0\] = 1
108
+ args\[1\] = 2
109
+ args\[2\] = 3
110
+ args\[3\] = 4
111
+ kwargs\[c\] = 5
112
+ kwargs\[d\] = 6
113
+ kwargs\[e\] = 7
114
+ Locals:
115
+ a = 2
116
+ b = 4
117
+ c = 10
118
+ args = \(6, 8\)
119
+ kwargs = {'d': 12, 'e': 14}
120
+ msg = 'Assertion failed: Result \(112\) must be divisible by 10'
121
+ Line 19:
122
+ raise ValueError\(msg\) from error
123
+ Raised:
124
+ builtins\.ValueError\(Assertion failed: Result \(112\) must be divisible by 10\)
125
+
126
+ Exception chain 2/2:
127
+ Frame 1/1: func_chain_second \(tests\.test_traceback_funcs\.chain\)
128
+ Inputs:
129
+ args\[0\] = 2
130
+ args\[1\] = 4
131
+ args\[2\] = 6
132
+ args\[3\] = 8
133
+ kwargs\[c\] = 10
134
+ kwargs\[d\] = 12
135
+ kwargs\[e\] = 14
136
+ Locals:
137
+ a = 4
138
+ b = 8
139
+ c = 20
140
+ args = \(12, 16\)
141
+ kwargs = {'d': 24, 'e': 28}
142
+ result = 112
143
+ Line 30:
144
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
145
+ Raised:
146
+ builtins\.AssertionError\(Result \(112\) must be divisible by 10\)
147
+ """
148
+ ),
149
+ flags=MULTILINE,
150
+ )
151
+
152
+
153
+ @fixture
154
+ def traceback_func_one() -> Pattern[str]:
155
+ return re.compile(
156
+ strip_and_dedent(
157
+ r"""
158
+ Date/time \| .+
159
+ Started \| .+
160
+ Duration \| .+
161
+ User \| .+
162
+ Host \| .+
163
+ Version \|\s
164
+
165
+ Frame 1/1: func_one \(tests\.test_traceback_funcs\.one\)
166
+ Inputs:
167
+ args\[0\] = 1
168
+ args\[1\] = 2
169
+ args\[2\] = 3
170
+ args\[3\] = 4
171
+ kwargs\[c\] = 5
172
+ kwargs\[d\] = 6
173
+ kwargs\[e\] = 7
174
+ Locals:
175
+ a = 2
176
+ b = 4
177
+ c = 10
178
+ args = \(6, 8\)
179
+ kwargs = {'d': 12, 'e': 14}
180
+ result = 56
181
+ Line 16:
182
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
183
+ Raised:
184
+ builtins\.AssertionError\(Result \(56\) must be divisible by 10\)
185
+ """
186
+ ),
187
+ flags=MULTILINE,
188
+ )
189
+
190
+
191
+ @fixture
192
+ def traceback_func_many_long() -> Pattern[str]:
193
+ return re.compile(
194
+ strip_and_dedent(
195
+ r"""
196
+ Date/time \| .+
197
+ Started \| .+
198
+ Duration \| .+
199
+ User \| .+
200
+ Host \| .+
201
+ Version \|\s
202
+
203
+ Frame 1/1: func_many \(tests.test_traceback_funcs.many\)
204
+ Inputs:
205
+ args\[0\] = 1
206
+ args\[1\] = 2
207
+ args\[2\] = 3
208
+ args\[3\] = 4
209
+ kwargs\[c\] = 5
210
+ kwargs\[d\] = 6
211
+ kwargs\[e\] = 7
212
+ Locals:
213
+ a = 2
214
+ b = 4
215
+ c = 10
216
+ args = \(
217
+ 6,
218
+ 8,
219
+ 0,
220
+ 2,
221
+ 4,
222
+ 6,
223
+ 8,
224
+ 10,
225
+ 12,
226
+ 14,
227
+ 16,
228
+ 18,
229
+ 20,
230
+ 22,
231
+ 24,
232
+ 26,
233
+ 28,
234
+ 30,
235
+ 32,
236
+ 34,
237
+ ... \+82
238
+ \)
239
+ kwargs = {'d': 12, 'e': 14}
240
+ result = 9956
241
+ Line 16:
242
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
243
+ Raised:
244
+ builtins\.AssertionError\(Result \(9956\) must be divisible by 10\)
245
+ """
246
+ ),
247
+ flags=MULTILINE,
248
+ )
249
+
250
+
251
+ @fixture
252
+ def traceback_func_many_short() -> Pattern[str]:
253
+ return re.compile(
254
+ strip_and_dedent(
255
+ r"""
256
+ Date/time \| .+
257
+ Started \| .+
258
+ Duration \| .+
259
+ User \| .+
260
+ Host \| .+
261
+ Version \|\s
262
+
263
+ Frame 1/1: func_many \(tests.test_traceback_funcs.many\)
264
+ Inputs:
265
+ args\[0\] = 1
266
+ args\[1\] = 2
267
+ args\[2\] = 3
268
+ args\[3\] = 4
269
+ kwargs\[c\] = 5
270
+ kwargs\[d\] = 6
271
+ kwargs\[e\] = 7
272
+ Locals:
273
+ a = 2
274
+ b = 4
275
+ c = 10
276
+ args = \(6, 8, 0, 2, 4, ... \+97\)
277
+ kwargs = {'d': 12, 'e': 14}
278
+ result = 9956
279
+ Line 16:
280
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
281
+ Raised:
282
+ builtins\.AssertionError\(Result \(9956\) must be divisible by 10\)
283
+ """
284
+ ),
285
+ flags=MULTILINE,
286
+ )
287
+
288
+
289
+ @fixture
290
+ def traceback_func_task_group_one() -> Pattern[str]:
291
+ return re.compile(
292
+ strip_and_dedent(
293
+ r"""
294
+ Date/time \| .+
295
+ Started \| .+
296
+ Duration \| .+
297
+ User \| .+
298
+ Host \| .+
299
+ Version \|\s
300
+
301
+ Exception group:
302
+ Frame 1/1: func_task_group_one_first \(tests\.test_traceback_funcs\.task_group_one\)
303
+ Inputs:
304
+ args\[0\] = 1
305
+ args\[1\] = 2
306
+ args\[2\] = 3
307
+ args\[3\] = 4
308
+ kwargs\[c\] = 5
309
+ kwargs\[d\] = 6
310
+ kwargs\[e\] = 7
311
+ Locals:
312
+ a = 2
313
+ b = 4
314
+ c = 10
315
+ args = \(6, 8\)
316
+ kwargs = {'d': 12, 'e': 14}
317
+ tg = <TaskGroup cancelling>
318
+ _ = <Task finished name='Task-\d+' coro=<func_task_group_one_second\(\) done, defined at .+src.+utilities.+traceback\.py:\d+> exception=AssertionError\('Result \(112\) must be divisible by 10'\)>
319
+ Line 18:
320
+ async with TaskGroup\(\) as tg:
321
+ Raised:
322
+ builtins\.ExceptionGroup\(unhandled errors in a TaskGroup \(1 sub-exception\)\)
323
+
324
+ Exception group error 1/1:
325
+ Frame 1/1: func_task_group_one_second \(tests\.test_traceback_funcs\.task_group_one\)
326
+ Inputs:
327
+ args\[0\] = 2
328
+ args\[1\] = 4
329
+ args\[2\] = 6
330
+ args\[3\] = 8
331
+ kwargs\[c\] = 10
332
+ kwargs\[d\] = 12
333
+ kwargs\[e\] = 14
334
+ Locals:
335
+ a = 4
336
+ b = 8
337
+ c = 20
338
+ args = \(12, 16\)
339
+ kwargs = {'d': 24, 'e': 28}
340
+ result = 112
341
+ Line 33:
342
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
343
+ Raised:
344
+ builtins\.AssertionError\(Result \(112\) must be divisible by 10\)
345
+ """
346
+ )
347
+ )
348
+
349
+
350
+ @fixture
351
+ def traceback_func_two() -> Pattern[str]:
352
+ return re.compile(
353
+ strip_and_dedent(
354
+ r"""
355
+ Date/time \| .+
356
+ Started \| .+
357
+ Duration \| .+
358
+ User \| .+
359
+ Host \| .+
360
+ Version \|\s
361
+
362
+ Frame 1/2: func_two_first \(tests\.test_traceback_funcs\.two\)
363
+ Inputs:
364
+ args\[0\] = 1
365
+ args\[1\] = 2
366
+ args\[2\] = 3
367
+ args\[3\] = 4
368
+ kwargs\[c\] = 5
369
+ kwargs\[d\] = 6
370
+ kwargs\[e\] = 7
371
+ Locals:
372
+ a = 2
373
+ b = 4
374
+ c = 10
375
+ args = \(6, 8\)
376
+ kwargs = {'d': 12, 'e': 14}
377
+ Line 15:
378
+ return func_two_second\(a, b, \*args, c=c, \*\*kwargs\)
379
+
380
+ Frame 2/2: func_two_second \(tests\.test_traceback_funcs\.two\)
381
+ Inputs:
382
+ args\[0\] = 2
383
+ args\[1\] = 4
384
+ args\[2\] = 6
385
+ args\[3\] = 8
386
+ kwargs\[c\] = 10
387
+ kwargs\[d\] = 12
388
+ kwargs\[e\] = 14
389
+ Locals:
390
+ a = 4
391
+ b = 8
392
+ c = 20
393
+ args = \(12, 16\)
394
+ kwargs = {'d': 24, 'e': 28}
395
+ result = 112
396
+ Line 26:
397
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
398
+ Raised:
399
+ builtins\.AssertionError\(Result \(112\) must be divisible by 10\)
400
+ """
401
+ ),
402
+ flags=MULTILINE,
403
+ )
404
+
405
+
406
+ @fixture
407
+ def traceback_func_untraced() -> Pattern[str]:
408
+ return re.compile(
409
+ strip_and_dedent(
410
+ r"""
411
+ Traceback \(most recent call last\):
412
+
413
+ File ".+src.+tests.+test_(logging|sys|traceback)\.py", line \d+, in test_.+
414
+ _ = func_untraced\(1, 2, 3, 4, c=5, d=6, e=7\)
415
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
416
+
417
+ File ".+src.+tests.+test_traceback_funcs.+untraced\.py", line 13, in func_untraced
418
+ assert result % 10 == 0, f"Result \({result}\) must be divisible by 10"
419
+ ^^^^^^^^^^^^^^^^
420
+
421
+ AssertionError: Result \(56\) must be divisible by 10
422
+ """
423
+ ).replace("^", r"\^"),
424
+ flags=MULTILINE,
425
+ )
@@ -23,14 +23,11 @@ from hypothesis.strategies import (
23
23
  sets,
24
24
  timedeltas,
25
25
  timezones,
26
- uuids,
27
26
  )
28
27
  from luigi import Task
29
28
  from numpy import inf, int64, isfinite, isinf, isnan, ravel, rint
30
29
  from pathvalidate import validate_filepath
31
30
  from pytest import mark, raises
32
- from sqlalchemy import Column, Integer, MetaData, Table, insert, select
33
- from sqlalchemy.ext.asyncio import AsyncEngine
34
31
 
35
32
  from tests.conftest import SKIPIF_CI_AND_WINDOWS
36
33
  from utilities.datetime import (
@@ -45,7 +42,6 @@ from utilities.datetime import (
45
42
  )
46
43
  from utilities.functions import ensure_int
47
44
  from utilities.hypothesis import (
48
- _SQLALCHEMY_ENGINE_DIALECTS,
49
45
  MaybeSearchStrategy,
50
46
  PlainDateTimesError,
51
47
  Shape,
@@ -83,7 +79,6 @@ from utilities.hypothesis import (
83
79
  settings_with_reduced_examples,
84
80
  setup_hypothesis_profiles,
85
81
  slices,
86
- sqlalchemy_engines,
87
82
  str_arrays,
88
83
  temp_dirs,
89
84
  temp_paths,
@@ -119,7 +114,6 @@ from utilities.math import (
119
114
  from utilities.os import temp_environ
120
115
  from utilities.platform import maybe_yield_lower_case
121
116
  from utilities.sentinel import Sentinel
122
- from utilities.sqlalchemy import Dialect, _get_dialect
123
117
  from utilities.version import Version
124
118
  from utilities.whenever import (
125
119
  MAX_SERIALIZABLE_TIMEDELTA,
@@ -134,7 +128,6 @@ from utilities.whenever import (
134
128
  if TYPE_CHECKING:
135
129
  from collections.abc import Iterable
136
130
  from collections.abc import Set as AbstractSet
137
- from uuid import UUID
138
131
  from zoneinfo import ZoneInfo
139
132
 
140
133
  from utilities.datetime import Month
@@ -917,38 +910,6 @@ class TestSlices:
917
910
  _ = data.draw(slices(iter_len, slice_len=iter_len + 1))
918
911
 
919
912
 
920
- class TestSQLAlchemyEngines:
921
- @given(
922
- data=data(),
923
- name=uuids(),
924
- dialect=_SQLALCHEMY_ENGINE_DIALECTS,
925
- ids=sets(integers(0, 10), min_size=1),
926
- )
927
- @settings(phases={Phase.generate})
928
- async def test_main(
929
- self, *, data: DataObject, name: UUID, dialect: Dialect, ids: set[int]
930
- ) -> None:
931
- table = Table(
932
- f"test_{name}", MetaData(), Column("id_", Integer, primary_key=True)
933
- )
934
- engine = await sqlalchemy_engines(data, table, dialect=dialect)
935
- assert isinstance(engine, AsyncEngine)
936
- assert _get_dialect(engine) == dialect
937
- if dialect == "sqlite":
938
- database = engine.url.database
939
- assert database is not None
940
- assert not Path(database).exists()
941
- async with engine.begin() as conn:
942
- await conn.run_sync(table.metadata.create_all)
943
- ins = insert(table).values([(id_,) for id_ in ids])
944
- async with engine.begin() as conn:
945
- _ = await conn.execute(ins)
946
- sel = select(table.c["id_"])
947
- async with engine.begin() as conn:
948
- results = (await conn.execute(sel)).scalars().all()
949
- assert set(results) == ids
950
-
951
-
952
913
  class TestStrArrays:
953
914
  @given(
954
915
  data=data(),
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from asyncio import TaskGroup, sleep
4
+ from re import search
5
+ from typing import TYPE_CHECKING
6
+
7
+ from pytest import mark, param, raises
8
+
9
+ from tests.conftest import SKIPIF_CI_AND_NOT_LINUX
10
+ from tests.test_redis import yield_test_redis
11
+ from utilities.iterables import one
12
+ from utilities.pottery import (
13
+ _YieldAccessNumLocksError,
14
+ _YieldAccessUnableToAcquireLockError,
15
+ yield_access,
16
+ )
17
+ from utilities.text import unique_str
18
+ from utilities.timer import Timer
19
+
20
+ if TYPE_CHECKING:
21
+ from redis.asyncio import Redis
22
+
23
+
24
+ async def _func_access(num_tasks: int, key: str, /, *, num_locks: int = 1) -> None:
25
+ async def coroutine() -> None:
26
+ async with yield_test_redis() as redis, yield_access(redis, key, num=num_locks):
27
+ await sleep(0.1)
28
+
29
+ async with TaskGroup() as tg:
30
+ _ = [tg.create_task(coroutine()) for _ in range(num_tasks)]
31
+
32
+
33
+ class TestYieldAccess:
34
+ @SKIPIF_CI_AND_NOT_LINUX
35
+ @mark.parametrize(
36
+ ("num_tasks", "num_locks", "min_time"),
37
+ [
38
+ param(1, 1, 0.1),
39
+ param(1, 2, 0.1),
40
+ param(1, 3, 0.1),
41
+ param(2, 1, 0.2),
42
+ param(2, 2, 0.1),
43
+ param(2, 3, 0.1),
44
+ param(2, 4, 0.1),
45
+ param(2, 5, 0.1),
46
+ param(3, 1, 0.3),
47
+ param(3, 2, 0.2),
48
+ param(3, 3, 0.1),
49
+ param(3, 4, 0.1),
50
+ param(3, 5, 0.1),
51
+ param(4, 1, 0.4),
52
+ param(4, 2, 0.2),
53
+ param(4, 3, 0.2),
54
+ param(4, 4, 0.1),
55
+ param(4, 5, 0.1),
56
+ ],
57
+ )
58
+ async def test_main(
59
+ self, *, num_tasks: int, num_locks: int, min_time: float
60
+ ) -> None:
61
+ with Timer() as timer:
62
+ await _func_access(num_tasks, unique_str(), num_locks=num_locks)
63
+ assert min_time <= float(timer) <= 3 * min_time
64
+
65
+ async def test_error_num_locks(self) -> None:
66
+ key = unique_str()
67
+ with raises(
68
+ _YieldAccessNumLocksError,
69
+ match=r"Number of locks for '\w+' must be positive; got 0",
70
+ ):
71
+ async with yield_test_redis() as redis, yield_access(redis, key, num=0):
72
+ ...
73
+
74
+ @SKIPIF_CI_AND_NOT_LINUX
75
+ async def test_error_unable_to_acquire_lock(self) -> None:
76
+ key = unique_str()
77
+
78
+ async def coroutine(redis: Redis, key: str, /) -> None:
79
+ async with yield_access(
80
+ redis, key, num=1, timeout_acquire=0.1, throttle=0.5
81
+ ):
82
+ await sleep(0.1)
83
+
84
+ with raises(ExceptionGroup) as exc_info: # noqa: PT012
85
+ async with yield_test_redis() as redis, TaskGroup() as tg:
86
+ _ = tg.create_task(coroutine(redis, key))
87
+ _ = tg.create_task(coroutine(redis, key))
88
+ error = one(exc_info.value.exceptions)
89
+ assert isinstance(error, _YieldAccessUnableToAcquireLockError)
90
+ assert search(
91
+ r"Unable to acquire any 1 of 1 locks for '\w+' after 0\.1", str(error)
92
+ )