dycw-utilities 0.130.1__tar.gz → 0.131.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 (220) hide show
  1. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/pyproject.toml +2 -2
  3. dycw_utilities-0.131.0/src/tests/test_pottery.py +92 -0
  4. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/__init__.py +1 -1
  5. dycw_utilities-0.131.0/src/utilities/pottery.py +116 -0
  6. dycw_utilities-0.130.1/src/tests/test_pottery.py +0 -28
  7. dycw_utilities-0.130.1/src/utilities/pottery.py +0 -50
  8. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/.gitignore +0 -0
  9. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/LICENSE +0 -0
  10. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/README.md +0 -0
  11. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/__init__.py +0 -0
  12. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/conftest.py +0 -0
  13. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/__init__.py +0 -0
  14. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_missing/__init__.py +0 -0
  15. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_missing/module.py +0 -0
  16. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/__init__.py +0 -0
  17. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/outer_1.py +0 -0
  18. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/outer_2.py +0 -0
  19. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  20. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  21. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  22. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  23. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/__init__.py +0 -0
  24. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/module_1.py +0 -0
  25. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/module_2.py +0 -0
  26. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/standalone.py +0 -0
  27. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/with_imports.py +0 -0
  28. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  29. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  30. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  31. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  32. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  33. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  34. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  35. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  36. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_aiolimiter.py +0 -0
  37. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_altair.py +0 -0
  38. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio.py +0 -0
  39. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/__init__.py +0 -0
  40. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/loopers.py +0 -0
  41. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/redis.py +0 -0
  42. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_atomicwrites.py +0 -0
  43. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_atools.py +0 -0
  44. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_cachetools.py +0 -0
  45. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_click.py +0 -0
  46. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_concurrent.py +0 -0
  47. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_contextlib.py +0 -0
  48. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_contextvars.py +0 -0
  49. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_cryptography.py +0 -0
  50. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_cvxpy.py +0 -0
  51. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_dataclasses.py +0 -0
  52. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_datetime.py +0 -0
  53. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_enum.py +0 -0
  54. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_errors.py +0 -0
  55. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_eventkit.py +0 -0
  56. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_fastapi.py +0 -0
  57. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_fpdf2.py +0 -0
  58. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_functions.py +0 -0
  59. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_functools.py +0 -0
  60. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_getpass.py +0 -0
  61. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_git.py +0 -0
  62. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_hashlib.py +0 -0
  63. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_http.py +0 -0
  64. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_hypothesis.py +0 -0
  65. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_importlib.py +0 -0
  66. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_inflect.py +0 -0
  67. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_ipython.py +0 -0
  68. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_iterables.py +0 -0
  69. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_jupyter.py +0 -0
  70. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_libcst.py +0 -0
  71. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_lightweight_charts.py +0 -0
  72. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_logging.py +0 -0
  73. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_luigi.py +0 -0
  74. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_math.py +0 -0
  75. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_memory_profiler.py +0 -0
  76. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_modules.py +0 -0
  77. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_more_itertools.py +0 -0
  78. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_numpy.py +0 -0
  79. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_operator.py +0 -0
  80. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_optuna.py +0 -0
  81. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_orjson.py +0 -0
  82. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_os.py +0 -0
  83. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_parse.py +0 -0
  84. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pathlib.py +0 -0
  85. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_period.py +0 -0
  86. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pickle.py +0 -0
  87. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_platform.py +0 -0
  88. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_polars.py +0 -0
  89. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_polars_ols.py +0 -0
  90. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pqdm.py +0 -0
  91. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_psutil.py +0 -0
  92. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pydantic.py +0 -0
  93. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pyinstrument.py +0 -0
  94. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pyrsistent.py +0 -0
  95. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pytest.py +0 -0
  96. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pytest_regressions.py +0 -0
  97. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_python_dotenv.py +0 -0
  98. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_random.py +0 -0
  99. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_re.py +0 -0
  100. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_redis.py +0 -0
  101. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_scipy.py +0 -0
  103. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_sentinel.py +0 -0
  104. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_shelve.py +0 -0
  105. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_slack_sdk.py +0 -0
  106. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_socket.py +0 -0
  107. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_sqlalchemy.py +0 -0
  108. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  109. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_statsmodel.py +0 -0
  110. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_streamlit.py +0 -0
  111. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_string.py +0 -0
  112. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tempfile.py +0 -0
  113. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tenacity.py +0 -0
  114. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_text.py +0 -0
  115. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_threading.py +0 -0
  116. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_timer.py +0 -0
  117. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_traceback.py +0 -0
  118. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_types.py +0 -0
  119. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing.py +0 -0
  120. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  121. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  122. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  123. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tzdata.py +0 -0
  124. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tzlocal.py +0 -0
  125. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_uuid.py +0 -0
  126. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_version.py +0 -0
  127. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_warnings.py +0 -0
  128. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_whenever.py +0 -0
  129. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_zipfile.py +0 -0
  130. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_zoneinfo.py +0 -0
  131. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/aiolimiter.py +0 -0
  132. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/altair.py +0 -0
  133. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/asyncio.py +0 -0
  134. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/atomicwrites.py +0 -0
  135. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/atools.py +0 -0
  136. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/cachetools.py +0 -0
  137. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/click.py +0 -0
  138. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/concurrent.py +0 -0
  139. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/contextlib.py +0 -0
  140. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/contextvars.py +0 -0
  141. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/cryptography.py +0 -0
  142. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/cvxpy.py +0 -0
  143. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/dataclasses.py +0 -0
  144. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/datetime.py +0 -0
  145. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/enum.py +0 -0
  146. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/errors.py +0 -0
  147. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/eventkit.py +0 -0
  148. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/fastapi.py +0 -0
  149. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/fpdf2.py +0 -0
  150. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/functions.py +0 -0
  151. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/functools.py +0 -0
  152. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/getpass.py +0 -0
  153. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/git.py +0 -0
  154. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/hashlib.py +0 -0
  155. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/http.py +0 -0
  156. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/hypothesis.py +0 -0
  157. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/importlib.py +0 -0
  158. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/inflect.py +0 -0
  159. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/ipython.py +0 -0
  160. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/iterables.py +0 -0
  161. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/jupyter.py +0 -0
  162. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/libcst.py +0 -0
  163. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/lightweight_charts.py +0 -0
  164. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/logging.py +0 -0
  165. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/luigi.py +0 -0
  166. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/math.py +0 -0
  167. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/memory_profiler.py +0 -0
  168. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/modules.py +0 -0
  169. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/more_itertools.py +0 -0
  170. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/numpy.py +0 -0
  171. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/operator.py +0 -0
  172. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/optuna.py +0 -0
  173. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/orjson.py +0 -0
  174. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/os.py +0 -0
  175. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/parse.py +0 -0
  176. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pathlib.py +0 -0
  177. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/period.py +0 -0
  178. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pickle.py +0 -0
  179. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/platform.py +0 -0
  180. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/polars.py +0 -0
  181. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/polars_ols.py +0 -0
  182. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pqdm.py +0 -0
  183. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/psutil.py +0 -0
  184. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/py.typed +0 -0
  185. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pydantic.py +0 -0
  186. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pyinstrument.py +0 -0
  187. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pyrsistent.py +0 -0
  188. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pytest.py +0 -0
  189. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pytest_regressions.py +0 -0
  190. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/python_dotenv.py +0 -0
  191. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/random.py +0 -0
  192. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/re.py +0 -0
  193. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/redis.py +0 -0
  194. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/reprlib.py +0 -0
  195. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/scipy.py +0 -0
  196. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/sentinel.py +0 -0
  197. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/shelve.py +0 -0
  198. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/slack_sdk.py +0 -0
  199. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/socket.py +0 -0
  200. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/sqlalchemy.py +0 -0
  201. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/sqlalchemy_polars.py +0 -0
  202. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/statsmodels.py +0 -0
  203. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/streamlit.py +0 -0
  204. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/string.py +0 -0
  205. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tempfile.py +0 -0
  206. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tenacity.py +0 -0
  207. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/text.py +0 -0
  208. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/threading.py +0 -0
  209. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/timer.py +0 -0
  210. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/traceback.py +0 -0
  211. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/types.py +0 -0
  212. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/typing.py +0 -0
  213. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tzdata.py +0 -0
  214. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tzlocal.py +0 -0
  215. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/uuid.py +0 -0
  216. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/version.py +0 -0
  217. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/warnings.py +0 -0
  218. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/whenever.py +0 -0
  219. {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/zipfile.py +0 -0
  220. {dycw_utilities-0.130.1 → dycw_utilities-0.131.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.130.1
3
+ Version: 0.131.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.130.1"
97
+ version = "0.131.0"
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.1"
344
+ current_version = "0.131.0"
345
345
 
346
346
  [[tool.bumpversion.files]]
347
347
  filename = "src/utilities/__init__.py"
@@ -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
+ )
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.130.1"
3
+ __version__ = "0.131.0"
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager, suppress
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, override
6
+
7
+ from pottery import AIORedlock
8
+ from pottery.exceptions import ReleaseUnlockedLock
9
+ from redis.asyncio import Redis
10
+
11
+ from utilities.asyncio import sleep_dur, timeout_dur
12
+ from utilities.datetime import MILLISECOND, SECOND, datetime_duration_to_float
13
+ from utilities.iterables import always_iterable
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import AsyncIterator, Iterable
17
+
18
+ from utilities.types import Duration, MaybeIterable
19
+
20
+
21
+ @asynccontextmanager
22
+ async def yield_access(
23
+ redis: MaybeIterable[Redis],
24
+ key: str,
25
+ /,
26
+ *,
27
+ num: int = 1,
28
+ timeout_acquire: Duration | None = None,
29
+ timeout_release: Duration = 10 * SECOND,
30
+ sleep: Duration = MILLISECOND,
31
+ throttle: Duration | None = None,
32
+ ) -> AsyncIterator[None]:
33
+ """Acquire access to a locked resource, amongst 1 of multiple connections."""
34
+ if num <= 0:
35
+ raise _YieldAccessNumLocksError(key=key, num=num)
36
+ masters = ( # skipif-ci-and-not-linux
37
+ {redis} if isinstance(redis, Redis) else set(always_iterable(redis))
38
+ )
39
+ auto_release_time = datetime_duration_to_float( # skipif-ci-and-not-linux
40
+ timeout_release
41
+ )
42
+ locks = [ # skipif-ci-and-not-linux
43
+ AIORedlock(
44
+ key=f"{key}_{i}_of_{num}",
45
+ masters=masters,
46
+ auto_release_time=auto_release_time,
47
+ )
48
+ for i in range(1, num + 1)
49
+ ]
50
+ lock: AIORedlock | None = None # skipif-ci-and-not-linux
51
+ try: # skipif-ci-and-not-linux
52
+ lock = await _get_first_available_lock(
53
+ key, locks, num=num, timeout=timeout_acquire, sleep=sleep
54
+ )
55
+ yield
56
+ finally: # skipif-ci-and-not-linux
57
+ await sleep_dur(duration=throttle)
58
+ if lock is not None:
59
+ with suppress(ReleaseUnlockedLock):
60
+ await lock.release()
61
+
62
+
63
+ async def _get_first_available_lock(
64
+ key: str,
65
+ locks: Iterable[AIORedlock],
66
+ /,
67
+ *,
68
+ num: int = 1,
69
+ timeout: Duration | None = None,
70
+ sleep: Duration | None = None,
71
+ ) -> AIORedlock:
72
+ locks = list(locks) # skipif-ci-and-not-linux
73
+ error = _YieldAccessUnableToAcquireLockError( # skipif-ci-and-not-linux
74
+ key=key, num=num, timeout=timeout
75
+ )
76
+ async with timeout_dur( # skipif-ci-and-not-linux
77
+ duration=timeout, error=error
78
+ ):
79
+ while True:
80
+ if (result := await _get_first_available_lock_if_any(locks)) is not None:
81
+ return result
82
+ await sleep_dur(duration=sleep)
83
+
84
+
85
+ async def _get_first_available_lock_if_any(
86
+ locks: Iterable[AIORedlock], /
87
+ ) -> AIORedlock | None:
88
+ for lock in locks: # skipif-ci-and-not-linux
89
+ if await lock.acquire(blocking=False):
90
+ return lock
91
+ return None # skipif-ci-and-not-linux
92
+
93
+
94
+ @dataclass(kw_only=True, slots=True)
95
+ class YieldAccessError(Exception):
96
+ key: str
97
+ num: int
98
+
99
+
100
+ @dataclass(kw_only=True, slots=True)
101
+ class _YieldAccessNumLocksError(YieldAccessError):
102
+ @override
103
+ def __str__(self) -> str:
104
+ return f"Number of locks for {self.key!r} must be positive; got {self.num}"
105
+
106
+
107
+ @dataclass(kw_only=True, slots=True)
108
+ class _YieldAccessUnableToAcquireLockError(YieldAccessError):
109
+ timeout: Duration | None
110
+
111
+ @override
112
+ def __str__(self) -> str:
113
+ return f"Unable to acquire any 1 of {self.num} locks for {self.key!r} after {self.timeout}" # skipif-ci-and-not-linux
114
+
115
+
116
+ __all__ = ["YieldAccessError", "yield_access"]
@@ -1,28 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from asyncio import TaskGroup, sleep
4
-
5
- from tests.conftest import SKIPIF_CI_AND_NOT_LINUX
6
- from tests.test_redis import yield_test_redis
7
- from utilities.pottery import yield_locked_resource
8
- from utilities.text import unique_str
9
- from utilities.timer import Timer
10
-
11
-
12
- async def _coroutine(key: str) -> None:
13
- async with yield_test_redis() as redis, yield_locked_resource(redis, key):
14
- await sleep(0.1)
15
-
16
-
17
- async def _asyncio_runner(num_tasks: int, key: str) -> None:
18
- async with TaskGroup() as tg:
19
- _ = [tg.create_task(_coroutine(key)) for _ in range(num_tasks)]
20
-
21
-
22
- class TestYieldLockedResource:
23
- @SKIPIF_CI_AND_NOT_LINUX
24
- async def test_single_process(self) -> None:
25
- with Timer() as timer:
26
- await _asyncio_runner(3, unique_str())
27
- min_time = 0.3
28
- assert min_time <= float(timer) <= 3 * min_time
@@ -1,50 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- from contextlib import asynccontextmanager, suppress
5
- from typing import TYPE_CHECKING
6
-
7
- from pottery import AIORedlock
8
- from pottery.exceptions import ReleaseUnlockedLock
9
- from redis.asyncio import Redis
10
-
11
- from utilities.datetime import MILLISECOND, SECOND, datetime_duration_to_float
12
- from utilities.iterables import always_iterable
13
-
14
- if TYPE_CHECKING:
15
- from collections.abc import AsyncIterator
16
-
17
- from utilities.types import Duration, MaybeIterable
18
-
19
-
20
- @asynccontextmanager
21
- async def yield_locked_resource(
22
- redis: MaybeIterable[Redis],
23
- key: str,
24
- /,
25
- *,
26
- duration: Duration = 10 * SECOND,
27
- sleep: Duration = MILLISECOND,
28
- ) -> AsyncIterator[None]:
29
- """Yield a locked resource."""
30
- masters = ( # skipif-ci-and-not-linux
31
- {redis} if isinstance(redis, Redis) else set(always_iterable(redis))
32
- )
33
- duration_use = datetime_duration_to_float(duration) # skipif-ci-and-not-linux
34
- lock = AIORedlock( # skipif-ci-and-not-linux
35
- key=key,
36
- masters=masters,
37
- auto_release_time=duration_use,
38
- context_manager_timeout=duration_use,
39
- )
40
- sleep_use = datetime_duration_to_float(sleep) # skipif-ci-and-not-linux
41
- while not await lock.acquire(): # pragma: no cover
42
- _ = await asyncio.sleep(sleep_use)
43
- try: # skipif-ci-and-not-linux
44
- yield
45
- finally: # skipif-ci-and-not-linux
46
- with suppress(ReleaseUnlockedLock):
47
- await lock.release()
48
-
49
-
50
- __all__ = ["yield_locked_resource"]