dycw-utilities 0.147.3__tar.gz → 0.148.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 (214) hide show
  1. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/pyproject.toml +2 -2
  3. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_asyncio.py +33 -10
  4. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pottery.py +3 -4
  5. dycw_utilities-0.148.0/src/tests/test_yield_access/script.py +61 -0
  6. dycw_utilities-0.148.0/src/tests/test_yield_access/script.sh +54 -0
  7. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/__init__.py +1 -1
  8. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/asyncio.py +36 -7
  9. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pottery.py +6 -35
  10. dycw_utilities-0.148.0/src/utilities/pytest_plugins/__init__.py +1 -0
  11. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/.gitignore +0 -0
  12. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/LICENSE +0 -0
  13. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/README.md +0 -0
  14. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/__init__.py +0 -0
  15. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/conftest.py +0 -0
  16. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/__init__.py +0 -0
  17. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_missing/__init__.py +0 -0
  18. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_missing/module.py +0 -0
  19. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/__init__.py +0 -0
  20. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/outer_1.py +0 -0
  21. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/outer_2.py +0 -0
  22. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  23. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  24. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  25. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  26. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_without/__init__.py +0 -0
  27. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_without/module_1.py +0 -0
  28. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/package_without/module_2.py +0 -0
  29. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/standalone.py +0 -0
  30. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/modules/with_imports.py +0 -0
  31. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  32. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  33. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  34. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  35. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  36. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  37. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  38. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  39. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_altair.py +0 -0
  40. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_atomicwrites.py +0 -0
  41. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_atools.py +0 -0
  42. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_cachetools.py +0 -0
  43. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_click.py +0 -0
  44. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_concurrent.py +0 -0
  45. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_contextlib.py +0 -0
  46. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_contextvars.py +0 -0
  47. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_cryptography.py +0 -0
  48. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_cvxpy.py +0 -0
  49. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_dataclasses.py +0 -0
  50. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_enum.py +0 -0
  51. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_errors.py +0 -0
  52. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_eventkit.py +0 -0
  53. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_fastapi.py +0 -0
  54. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_fpdf2.py +0 -0
  55. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_functions.py +0 -0
  56. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_functools.py +0 -0
  57. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_getpass.py +0 -0
  58. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_gzip.py +0 -0
  59. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_hashlib.py +0 -0
  60. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_http.py +0 -0
  61. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_hypothesis.py +0 -0
  62. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_importlib.py +0 -0
  63. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_inflect.py +0 -0
  64. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_ipython.py +0 -0
  65. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_iterables.py +0 -0
  66. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_json.py +0 -0
  67. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_jupyter.py +0 -0
  68. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_libcst.py +0 -0
  69. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_lightweight_charts.py +0 -0
  70. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_logging.py +0 -0
  71. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_math.py +0 -0
  72. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_memory_profiler.py +0 -0
  73. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_modules.py +0 -0
  74. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_more_itertools.py +0 -0
  75. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_numpy.py +0 -0
  76. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_objects/__init__.py +0 -0
  77. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_objects/objects.py +0 -0
  78. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_operator.py +0 -0
  79. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_optuna.py +0 -0
  80. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_orjson.py +0 -0
  81. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_os.py +0 -0
  82. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_parse.py +0 -0
  83. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pathlib.py +0 -0
  84. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_period.py +0 -0
  85. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pickle.py +0 -0
  86. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_platform.py +0 -0
  87. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_polars.py +0 -0
  88. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_polars_ols.py +0 -0
  89. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_postgres.py +0 -0
  90. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pqdm.py +0 -0
  91. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_psutil.py +0 -0
  92. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pyinstrument.py +0 -0
  93. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pytest.py +0 -0
  94. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pytest_randomly.py +0 -0
  95. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_pytest_regressions.py +0 -0
  96. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_random.py +0 -0
  97. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_re.py +0 -0
  98. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_redis.py +0 -0
  99. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_reprlib.py +0 -0
  100. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_scipy.py +0 -0
  101. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_sentinel.py +0 -0
  102. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_shelve.py +0 -0
  103. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_slack_sdk.py +0 -0
  104. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_socket.py +0 -0
  105. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_sqlalchemy.py +0 -0
  106. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  107. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_statsmodels.py +0 -0
  108. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_string.py +0 -0
  109. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_tempfile.py +0 -0
  110. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_text.py +0 -0
  111. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_threading.py +0 -0
  112. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_timer.py +0 -0
  113. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_traceback.py +0 -0
  114. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_typed_settings.py +0 -0
  115. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_types.py +0 -0
  116. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_typing.py +0 -0
  117. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  118. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  119. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  120. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_tzdata.py +0 -0
  121. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_tzlocal.py +0 -0
  122. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_uuid.py +0 -0
  123. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_version.py +0 -0
  124. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_warnings.py +0 -0
  125. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_whenever.py +0 -0
  126. {dycw_utilities-0.147.3/src/utilities/pytest_plugins → dycw_utilities-0.148.0/src/tests/test_yield_access}/__init__.py +0 -0
  127. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_zipfile.py +0 -0
  128. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/tests/test_zoneinfo.py +0 -0
  129. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/altair.py +0 -0
  130. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/atomicwrites.py +0 -0
  131. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/atools.py +0 -0
  132. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/cachetools.py +0 -0
  133. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/click.py +0 -0
  134. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/concurrent.py +0 -0
  135. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/contextlib.py +0 -0
  136. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/contextvars.py +0 -0
  137. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/cryptography.py +0 -0
  138. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/cvxpy.py +0 -0
  139. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/dataclasses.py +0 -0
  140. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/enum.py +0 -0
  141. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/errors.py +0 -0
  142. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/eventkit.py +0 -0
  143. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/fastapi.py +0 -0
  144. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/fpdf2.py +0 -0
  145. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/functions.py +0 -0
  146. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/functools.py +0 -0
  147. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/getpass.py +0 -0
  148. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/gzip.py +0 -0
  149. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/hashlib.py +0 -0
  150. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/http.py +0 -0
  151. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/hypothesis.py +0 -0
  152. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/importlib.py +0 -0
  153. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/inflect.py +0 -0
  154. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/ipython.py +0 -0
  155. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/iterables.py +0 -0
  156. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/json.py +0 -0
  157. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/jupyter.py +0 -0
  158. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/libcst.py +0 -0
  159. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/lightweight_charts.py +0 -0
  160. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/logging.py +0 -0
  161. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/math.py +0 -0
  162. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/memory_profiler.py +0 -0
  163. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/modules.py +0 -0
  164. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/more_itertools.py +0 -0
  165. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/numpy.py +0 -0
  166. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/operator.py +0 -0
  167. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/optuna.py +0 -0
  168. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/orjson.py +0 -0
  169. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/os.py +0 -0
  170. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/parse.py +0 -0
  171. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pathlib.py +0 -0
  172. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/period.py +0 -0
  173. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pickle.py +0 -0
  174. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/platform.py +0 -0
  175. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/polars.py +0 -0
  176. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/polars_ols.py +0 -0
  177. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/postgres.py +0 -0
  178. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pqdm.py +0 -0
  179. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/psutil.py +0 -0
  180. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/py.typed +0 -0
  181. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pyinstrument.py +0 -0
  182. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pytest.py +0 -0
  183. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  184. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  185. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/pytest_regressions.py +0 -0
  186. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/random.py +0 -0
  187. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/re.py +0 -0
  188. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/redis.py +0 -0
  189. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/reprlib.py +0 -0
  190. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/scipy.py +0 -0
  191. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/sentinel.py +0 -0
  192. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/shelve.py +0 -0
  193. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/slack_sdk.py +0 -0
  194. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/socket.py +0 -0
  195. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/sqlalchemy.py +0 -0
  196. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/sqlalchemy_polars.py +0 -0
  197. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/statsmodels.py +0 -0
  198. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/string.py +0 -0
  199. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/tempfile.py +0 -0
  200. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/text.py +0 -0
  201. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/threading.py +0 -0
  202. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/timer.py +0 -0
  203. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/traceback.py +0 -0
  204. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/typed_settings.py +0 -0
  205. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/types.py +0 -0
  206. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/typing.py +0 -0
  207. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/tzdata.py +0 -0
  208. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/tzlocal.py +0 -0
  209. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/uuid.py +0 -0
  210. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/version.py +0 -0
  211. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/warnings.py +0 -0
  212. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/whenever.py +0 -0
  213. {dycw_utilities-0.147.3 → dycw_utilities-0.148.0}/src/utilities/zipfile.py +0 -0
  214. {dycw_utilities-0.147.3 → dycw_utilities-0.148.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.147.3
3
+ Version: 0.148.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -102,7 +102,7 @@ dependencies = [
102
102
  name = "dycw-utilities"
103
103
  readme = "README.md"
104
104
  requires-python = ">= 3.12"
105
- version = "0.147.3"
105
+ version = "0.148.0"
106
106
 
107
107
  [project.entry-points.pytest11]
108
108
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -135,7 +135,7 @@ test = [
135
135
  # bump-my-version
136
136
  [tool.bumpversion]
137
137
  allow_dirty = true
138
- current_version = "0.147.3"
138
+ current_version = "0.148.0"
139
139
 
140
140
  [[tool.bumpversion.files]]
141
141
  filename = "src/utilities/__init__.py"
@@ -8,11 +8,12 @@ from typing import TYPE_CHECKING, ClassVar
8
8
 
9
9
  from hypothesis import given
10
10
  from hypothesis.strategies import booleans, dictionaries, integers, lists, none
11
- from pytest import RaisesGroup, raises
11
+ from pytest import LogCaptureFixture, RaisesGroup, mark, param, raises
12
12
 
13
13
  from utilities.asyncio import (
14
14
  AsyncDict,
15
15
  EnhancedTaskGroup,
16
+ get_coroutine_name,
16
17
  get_items,
17
18
  get_items_nowait,
18
19
  loop_until_succeed,
@@ -27,6 +28,7 @@ from utilities.asyncio import (
27
28
  )
28
29
  from utilities.hypothesis import pairs, text_ascii
29
30
  from utilities.pytest import skipif_windows
31
+ from utilities.text import unique_str
30
32
  from utilities.timer import Timer
31
33
  from utilities.whenever import MILLISECOND, SECOND, get_now
32
34
 
@@ -287,6 +289,16 @@ class TestEnhancedTaskGroup:
287
289
  _ = tg.create_task(sleep_td(2 * self.delta))
288
290
 
289
291
 
292
+ class TestGetCoroutineName:
293
+ def test_main(self) -> None:
294
+ async def func() -> None:
295
+ return None
296
+
297
+ result = get_coroutine_name(func)
298
+ expected = "func"
299
+ assert result == expected
300
+
301
+
290
302
  class TestGetItems:
291
303
  @given(
292
304
  xs=lists(integers(), min_size=1),
@@ -306,7 +318,12 @@ class TestGetItems:
306
318
 
307
319
 
308
320
  class TestLoopUntilSucceed:
309
- async def test_main(self) -> None:
321
+ @mark.parametrize("sleep", [param(MILLISECOND), param(None)])
322
+ @mark.parametrize("use_logger", [param(True), param(False)])
323
+ async def test_main(
324
+ self, *, caplog: LogCaptureFixture, sleep: TimeDelta | None, use_logger: bool
325
+ ) -> None:
326
+ caplog.set_level("DEBUG", logger=(name := unique_str()))
310
327
  counter = 0
311
328
 
312
329
  async def func() -> None:
@@ -315,12 +332,22 @@ class TestLoopUntilSucceed:
315
332
  if counter <= 3:
316
333
  raise ValueError
317
334
 
318
- _ = await loop_until_succeed(lambda: func())
335
+ assert await loop_until_succeed(
336
+ lambda: func(), logger=name if use_logger else None, sleep=sleep
337
+ )
319
338
  assert counter == 4
320
339
 
340
+ if use_logger:
341
+ messages = [r.message for r in caplog.records if r.name == name]
342
+ expected = 3 * (
343
+ ["Error running 'func'"]
344
+ + ([] if sleep is None else ["Sleeping for PT0.001S..."])
345
+ + ["Retrying 'func'..."]
346
+ )
347
+ assert messages == expected
348
+
321
349
  async def test_error(self) -> None:
322
350
  counter = 0
323
- errors: list[Exception] = []
324
351
 
325
352
  async def func() -> None:
326
353
  nonlocal counter
@@ -328,12 +355,8 @@ class TestLoopUntilSucceed:
328
355
  if counter <= 3:
329
356
  raise ValueError
330
357
 
331
- def error(error: Exception, /) -> None:
332
- errors.append(error)
333
-
334
- _ = await loop_until_succeed(lambda: func(), error=error)
335
- assert counter == 4
336
- assert len(errors) == 3
358
+ assert not await loop_until_succeed(lambda: func(), errors=ValueError)
359
+ assert counter == 1
337
360
 
338
361
 
339
362
  class TestPutItems:
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from asyncio import TaskGroup
4
- from itertools import repeat
5
4
  from typing import TYPE_CHECKING, ClassVar
6
5
 
7
6
  from pottery import AIORedlock
@@ -74,7 +73,7 @@ class TestTryYieldCoroutineLooper:
74
73
  redis, key, timeout_acquire=self.delta, logger=logger
75
74
  ) as looper:
76
75
  if looper is not None:
77
- await looper(self.func_main, lst)
76
+ assert await looper(self.func_main, lst)
78
77
 
79
78
  async def delayed(
80
79
  self,
@@ -102,11 +101,11 @@ class TestTryYieldCoroutineLooper:
102
101
  logger=name if use_logger else None,
103
102
  ) as looper:
104
103
  assert looper is not None
105
- await looper(self.func_error, lst)
104
+ assert await looper(self.func_error, lst)
106
105
 
107
106
  if use_logger:
108
107
  messages = [r.message for r in caplog.records if r.name == name]
109
- expected = list(repeat("Error running 'func_error'", times=3))
108
+ expected = 3 * ["Error running 'func_error'", "Retrying 'func_error'..."]
110
109
  assert messages == expected
111
110
 
112
111
  async def func_error(self, lst: list[None], /) -> None:
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from asyncio import run
4
+ from logging import getLogger
5
+ from random import randint
6
+ from typing import TYPE_CHECKING
7
+
8
+ from redis.asyncio import Redis
9
+
10
+ from utilities.asyncio import sleep_td
11
+ from utilities.logging import setup_logging
12
+ from utilities.pathlib import get_repo_root
13
+ from utilities.pottery import extend_lock, try_yield_coroutine_looper
14
+ from utilities.whenever import SECOND
15
+
16
+ if TYPE_CHECKING:
17
+ from pottery import AIORedlock
18
+
19
+ _LOGGER = getLogger(__name__)
20
+
21
+
22
+ async def script(*, lock: AIORedlock | None = None) -> None:
23
+ total = 1000
24
+ fail = 30
25
+ success = 3
26
+ while True:
27
+ n = randint(0, total)
28
+ if n < fail:
29
+ _LOGGER.info("n = %d; failing...", n)
30
+ msg = f"n = {n}; failure"
31
+ raise ValueError(msg)
32
+ if fail <= n < (fail + success):
33
+ _LOGGER.info("n = %d; succeeding...", n)
34
+ return
35
+ _LOGGER.info("n = %d", n)
36
+ await extend_lock(lock=lock)
37
+ await sleep_td(SECOND / 3)
38
+
39
+
40
+ async def service() -> None:
41
+ redis = Redis()
42
+ async with try_yield_coroutine_looper(
43
+ redis,
44
+ "utilities-test",
45
+ num=1,
46
+ timeout_release=5 * SECOND,
47
+ logger=_LOGGER,
48
+ sleep_error=4 * SECOND,
49
+ ) as looper:
50
+ if looper is not None:
51
+ result = await looper(script, lock=looper.lock)
52
+ _LOGGER.info("script %s", "succeeded" if result else "failed")
53
+
54
+
55
+ def main() -> None:
56
+ setup_logging(logger=_LOGGER, files_dir=get_repo_root().joinpath(".logs"))
57
+ run(service())
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env sh
2
+
3
+ # Add to your `crontab` as:
4
+ # * * * * * /Users/derekwan/work/python-utilities/tests/test_yield_access/script.sh
5
+
6
+ # helpers
7
+ echo_pid_date() { echo "[$$ | $(date +'%Y-%m-%d %H:%M:%S')] $*"; }
8
+
9
+ # add to `$PATH`
10
+ for __dir in "${HOME}/.local/bin" '/opt/homebrew/bin' '/opt/homebrew/opt/postgresql@17/bin'; do
11
+ if [ -d "${__dir}" ]; then
12
+ case ":${PATH}:" in
13
+ *:"$__dir":*) ;;
14
+ *)
15
+ export PATH="${__dir}:${PATH}"
16
+ ;;
17
+ esac
18
+ fi
19
+ done
20
+
21
+ # log file
22
+ __package_dir="${HOME}/work/python-utilities"
23
+ __log_file="${__package_dir}/.logs/test-yield-access"
24
+
25
+ # check if binaries are accessible
26
+ for __app in direnv just uv; do
27
+ if ! command -v "${__app}" >/dev/null 2>&1; then
28
+ echo_pid_date "ERROR: Command '${__app}' not found; exiting..." 2>&1 | tee -a "${__log_file}"
29
+ exit
30
+ fi
31
+ done
32
+
33
+ # enter package
34
+ echo_pid_date "Entering package directory '${__package_dir}'..." 2>&1 | tee -a "${__log_file}"
35
+ cd "${__package_dir}" || exit
36
+
37
+ # trim log
38
+ __threshold=$((10 * 1024 * 1024))
39
+ if [ -f "${__log_file}" ]; then
40
+ __log_size=$(wc -c <"${__log_file}")
41
+ if [ "${__log_size}" -gt "${__threshold}" ]; then
42
+ echo_pid_date "Truncating log file '${__log_file}' (log size = ${__log_size})..." 2>&1 | tee -a "${__log_file}"
43
+ __total_lines=$(wc -l <"${__log_file}")
44
+ __keep_lines=$((__total_lines / 2))
45
+ __tmp_log_file="${__log_file}.tmp.$$"
46
+ tail -n "${__keep_lines}" "${__log_file}" >"${__tmp_log_file}" && mv "${__tmp_log_file}" "${__log_file}"
47
+ fi
48
+ fi
49
+
50
+ # run the script
51
+ echo_pid_date "Running 'PYTHONPATH=src/tests/test_yield_access python -m script'..." 2>&1 | tee -a "${__log_file}"
52
+ __start="$(date +%s)"
53
+ PYTHONPATH=src/tests/test_yield_access direnv exec . python -m script "$*" 2>&1 | tee -a "${__log_file}"
54
+ __exit_code="$?"
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.147.3"
3
+ __version__ = "0.148.0"
@@ -37,8 +37,10 @@ from typing import (
37
37
 
38
38
  from utilities.errors import ImpossibleCaseError
39
39
  from utilities.functions import ensure_int, ensure_not_none, to_bool
40
+ from utilities.logging import get_logger
40
41
  from utilities.random import SYSTEM_RANDOM
41
42
  from utilities.sentinel import Sentinel, sentinel
43
+ from utilities.warnings import suppress_warnings
42
44
  from utilities.whenever import get_now, round_date_or_date_time, to_nanoseconds
43
45
 
44
46
  if TYPE_CHECKING:
@@ -63,6 +65,7 @@ if TYPE_CHECKING:
63
65
  from utilities.types import (
64
66
  Coro,
65
67
  Delta,
68
+ LoggerOrName,
66
69
  MaybeCallableBool,
67
70
  MaybeType,
68
71
  SupportsKeysAndGetItem,
@@ -340,6 +343,20 @@ class EnhancedTaskGroup(TaskGroup):
340
343
  ##
341
344
 
342
345
 
346
+ def get_coroutine_name(func: Callable[[], Coro[Any]], /) -> str:
347
+ """Get the name of a coroutine, and then dispose of it gracefully."""
348
+ coro = func()
349
+ name = coro.__name__
350
+ with suppress_warnings(
351
+ message="coroutine '.*' was never awaited", category=RuntimeWarning
352
+ ):
353
+ del coro
354
+ return name
355
+
356
+
357
+ ##
358
+
359
+
343
360
  async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> list[T]:
344
361
  """Get items from a queue; if empty then wait."""
345
362
  try:
@@ -380,23 +397,34 @@ async def loop_until_succeed(
380
397
  func: Callable[[], Coro[None]],
381
398
  /,
382
399
  *,
383
- error: Callable[[Exception], None] | None = None,
400
+ logger: LoggerOrName | None = None,
401
+ errors: type[Exception] | tuple[type[Exception], ...] | None = None,
384
402
  sleep: Delta | None = None,
385
- ) -> None:
403
+ ) -> bool:
386
404
  """Repeatedly call a coroutine until it succeeds."""
405
+ name = get_coroutine_name(func)
387
406
  while True:
388
407
  try:
389
- return await func()
390
- except Exception as err: # noqa: BLE001
391
- if error is not None:
392
- error(err)
408
+ await func()
409
+ except Exception as error: # noqa: BLE001
410
+ if logger is not None:
411
+ get_logger(logger=logger).error("Error running %r", name, exc_info=True)
393
412
  exc_type, exc_value, traceback = sys.exc_info()
394
413
  if (exc_type is None) or (exc_value is None): # pragma: no cover
395
414
  raise ImpossibleCaseError(
396
415
  case=[f"{exc_type=}", f"{exc_value=}"]
397
416
  ) from None
398
417
  sys.excepthook(exc_type, exc_value, traceback)
399
- await sleep_td(sleep)
418
+ if (errors is not None) and isinstance(error, errors):
419
+ return False
420
+ if sleep is not None:
421
+ if logger is not None:
422
+ get_logger(logger=logger).info("Sleeping for %s...", sleep)
423
+ await sleep_td(sleep)
424
+ if logger is not None:
425
+ get_logger(logger=logger).info("Retrying %r...", name)
426
+ else:
427
+ return True
400
428
 
401
429
 
402
430
  ##
@@ -522,6 +550,7 @@ __all__ = [
522
550
  "AsyncDict",
523
551
  "EnhancedTaskGroup",
524
552
  "StreamCommandOutput",
553
+ "get_coroutine_name",
525
554
  "get_items",
526
555
  "get_items_nowait",
527
556
  "loop_until_succeed",
@@ -5,16 +5,14 @@ from dataclasses import dataclass
5
5
  from sys import maxsize
6
6
  from typing import TYPE_CHECKING, override
7
7
 
8
- from pottery import AIORedlock
8
+ from pottery import AIORedlock, ExtendUnlockedLock
9
9
  from pottery.exceptions import ReleaseUnlockedLock
10
10
  from redis.asyncio import Redis
11
11
 
12
12
  from utilities.asyncio import loop_until_succeed, sleep_td, timeout_td
13
13
  from utilities.contextlib import enhanced_async_context_manager
14
- from utilities.functools import partial
15
14
  from utilities.iterables import always_iterable
16
15
  from utilities.logging import get_logger
17
- from utilities.warnings import suppress_warnings
18
16
  from utilities.whenever import MILLISECOND, SECOND, to_seconds
19
17
 
20
18
  if TYPE_CHECKING:
@@ -72,10 +70,7 @@ async def try_yield_coroutine_looper(
72
70
  throttle=throttle,
73
71
  ) as lock:
74
72
  yield CoroutineLooper(lock=lock, logger=logger, sleep=sleep_error)
75
- except ( # skipif-ci-and-not-linux
76
- _YieldAccessUnableToAcquireLockError,
77
- _YieldAccessAcquiredUnlockedLockError,
78
- ) as error:
73
+ except _YieldAccessUnableToAcquireLockError as error: # skipif-ci-and-not-linux
79
74
  if logger is not None:
80
75
  get_logger(logger=logger).info("%s", error)
81
76
  async with nullcontext():
@@ -92,27 +87,14 @@ class CoroutineLooper:
92
87
 
93
88
  async def __call__[**P](
94
89
  self, func: Callable[P, Coro[None]], *args: P.args, **kwargs: P.kwargs
95
- ) -> None:
90
+ ) -> bool:
96
91
  def make_coro() -> Coro[None]:
97
92
  return func(*args, **kwargs)
98
93
 
99
- await loop_until_succeed(
100
- make_coro, error=partial(self._error, func=make_coro), sleep=self.sleep
94
+ return await loop_until_succeed(
95
+ make_coro, logger=self.logger, errors=ExtendUnlockedLock, sleep=self.sleep
101
96
  )
102
97
 
103
- def _error(self, error: Exception, /, *, func: Callable[[], Coro[None]]) -> None:
104
- _ = error
105
- if self.logger is not None:
106
- coro = func()
107
- name = coro.__name__ # skipif-ci-and-not-linux
108
- with suppress_warnings(
109
- message="coroutine '.*' was never awaited", category=RuntimeWarning
110
- ):
111
- del coro
112
- get_logger(logger=self.logger).error(
113
- "Error running %r", name, exc_info=True
114
- )
115
-
116
98
 
117
99
  ##
118
100
 
@@ -150,8 +132,6 @@ async def yield_access(
150
132
  lock = await _get_first_available_lock(
151
133
  key, locks, num=num, timeout=timeout_acquire, sleep=sleep
152
134
  )
153
- if (await lock.locked()) == 0.0: # pragma: no cover
154
- raise _YieldAccessAcquiredUnlockedLockError(key=lock.key)
155
135
  yield lock
156
136
  finally: # skipif-ci-and-not-linux
157
137
  await sleep_td(throttle)
@@ -175,9 +155,7 @@ async def _get_first_available_lock(
175
155
  )
176
156
  async with timeout_td(timeout, error=error): # skipif-ci-and-not-linux
177
157
  while True:
178
- if (
179
- (result := await _get_first_available_lock_if_any(locks)) is not None
180
- ) and (await result.locked() > 0.0):
158
+ if (result := await _get_first_available_lock_if_any(locks)) is not None:
181
159
  return result
182
160
  await sleep_td(sleep)
183
161
 
@@ -215,13 +193,6 @@ class _YieldAccessUnableToAcquireLockError(YieldAccessError):
215
193
  return f"Unable to acquire any 1 of {self.num} locks for {self.key!r} after {self.timeout}" # skipif-ci-and-not-linux
216
194
 
217
195
 
218
- @dataclass(kw_only=True, slots=True)
219
- class _YieldAccessAcquiredUnlockedLockError(YieldAccessError):
220
- @override
221
- def __str__(self) -> str:
222
- return f"Acquired an unlocked lock {self.key!r}" # pragma: no cover
223
-
224
-
225
196
  __all__ = [
226
197
  "CoroutineLooper",
227
198
  "YieldAccessError",
@@ -0,0 +1 @@
1
+ from __future__ import annotations