dycw-utilities 0.132.4__tar.gz → 0.133.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 (218) hide show
  1. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/PKG-INFO +1 -1
  2. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/pyproject.toml +6 -3
  3. dycw_utilities-0.133.1/src/tests/test_arq.py +47 -0
  4. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_click.py +3 -0
  5. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_hypothesis.py +13 -1
  6. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_typed_settings.py +3 -0
  7. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_whenever.py +74 -0
  8. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/__init__.py +1 -1
  9. dycw_utilities-0.133.1/src/utilities/arq.py +161 -0
  10. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/click.py +26 -1
  11. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/hypothesis.py +36 -0
  12. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/typed_settings.py +2 -0
  13. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/types.py +6 -0
  14. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/whenever.py +129 -2
  15. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/.gitignore +0 -0
  16. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/LICENSE +0 -0
  17. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/README.md +0 -0
  18. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/__init__.py +0 -0
  19. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/conftest.py +0 -0
  20. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/__init__.py +0 -0
  21. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_missing/__init__.py +0 -0
  22. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_missing/module.py +0 -0
  23. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/__init__.py +0 -0
  24. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/outer_1.py +0 -0
  25. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/outer_2.py +0 -0
  26. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  27. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  28. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  29. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  30. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_without/__init__.py +0 -0
  31. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_without/module_1.py +0 -0
  32. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/package_without/module_2.py +0 -0
  33. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/standalone.py +0 -0
  34. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/modules/with_imports.py +0 -0
  35. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  36. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  37. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  38. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  39. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  40. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  41. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  42. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  43. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_aiolimiter.py +0 -0
  44. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_altair.py +0 -0
  45. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_asyncio.py +0 -0
  46. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_asyncio_classes/__init__.py +0 -0
  47. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_asyncio_classes/loopers.py +0 -0
  48. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_asyncio_classes/redis.py +0 -0
  49. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_atomicwrites.py +0 -0
  50. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_atools.py +0 -0
  51. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_cachetools.py +0 -0
  52. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_concurrent.py +0 -0
  53. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_contextlib.py +0 -0
  54. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_contextvars.py +0 -0
  55. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_cryptography.py +0 -0
  56. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_cvxpy.py +0 -0
  57. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_dataclasses.py +0 -0
  58. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_enum.py +0 -0
  59. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_errors.py +0 -0
  60. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_eventkit.py +0 -0
  61. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_fastapi.py +0 -0
  62. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_fpdf2.py +0 -0
  63. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_functions.py +0 -0
  64. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_functools.py +0 -0
  65. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_getpass.py +0 -0
  66. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_git.py +0 -0
  67. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_hashlib.py +0 -0
  68. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_http.py +0 -0
  69. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_importlib.py +0 -0
  70. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_inflect.py +0 -0
  71. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_ipython.py +0 -0
  72. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_iterables.py +0 -0
  73. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_jupyter.py +0 -0
  74. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_libcst.py +0 -0
  75. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_lightweight_charts.py +0 -0
  76. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_logging.py +0 -0
  77. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_luigi.py +0 -0
  78. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_math.py +0 -0
  79. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_memory_profiler.py +0 -0
  80. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_modules.py +0 -0
  81. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_more_itertools.py +0 -0
  82. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_numpy.py +0 -0
  83. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_objects/__init__.py +0 -0
  84. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_objects/objects.py +0 -0
  85. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_operator.py +0 -0
  86. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_optuna.py +0 -0
  87. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_orjson.py +0 -0
  88. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_os.py +0 -0
  89. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_parse.py +0 -0
  90. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pathlib.py +0 -0
  91. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_period.py +0 -0
  92. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pickle.py +0 -0
  93. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_platform.py +0 -0
  94. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_polars.py +0 -0
  95. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_polars_ols.py +0 -0
  96. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pottery.py +0 -0
  97. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pqdm.py +0 -0
  98. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_psutil.py +0 -0
  99. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pydantic.py +0 -0
  100. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pyinstrument.py +0 -0
  101. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pytest.py +0 -0
  102. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_pytest_regressions.py +0 -0
  103. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_python_dotenv.py +0 -0
  104. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_random.py +0 -0
  105. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_re.py +0 -0
  106. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_redis.py +0 -0
  107. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_reprlib.py +0 -0
  108. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_scipy.py +0 -0
  109. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_sentinel.py +0 -0
  110. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_shelve.py +0 -0
  111. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_slack_sdk.py +0 -0
  112. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_socket.py +0 -0
  113. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_sqlalchemy.py +0 -0
  114. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_sqlalchemy_polars.py +0 -0
  115. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_statsmodels.py +0 -0
  116. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_streamlit.py +0 -0
  117. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_string.py +0 -0
  118. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_tempfile.py +0 -0
  119. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_text.py +0 -0
  120. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_threading.py +0 -0
  121. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_timer.py +0 -0
  122. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_traceback.py +0 -0
  123. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_types.py +0 -0
  124. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_typing.py +0 -0
  125. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_typing_funcs/__init__.py +0 -0
  126. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_typing_funcs/no_future.py +0 -0
  127. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_typing_funcs/with_future.py +0 -0
  128. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_tzdata.py +0 -0
  129. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_tzlocal.py +0 -0
  130. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_uuid.py +0 -0
  131. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_version.py +0 -0
  132. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_warnings.py +0 -0
  133. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_zipfile.py +0 -0
  134. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/tests/test_zoneinfo.py +0 -0
  135. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/aiolimiter.py +0 -0
  136. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/altair.py +0 -0
  137. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/asyncio.py +0 -0
  138. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/atomicwrites.py +0 -0
  139. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/atools.py +0 -0
  140. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/cachetools.py +0 -0
  141. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/concurrent.py +0 -0
  142. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/contextlib.py +0 -0
  143. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/contextvars.py +0 -0
  144. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/cryptography.py +0 -0
  145. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/cvxpy.py +0 -0
  146. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/dataclasses.py +0 -0
  147. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/enum.py +0 -0
  148. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/errors.py +0 -0
  149. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/eventkit.py +0 -0
  150. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/fastapi.py +0 -0
  151. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/fpdf2.py +0 -0
  152. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/functions.py +0 -0
  153. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/functools.py +0 -0
  154. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/getpass.py +0 -0
  155. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/git.py +0 -0
  156. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/hashlib.py +0 -0
  157. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/http.py +0 -0
  158. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/importlib.py +0 -0
  159. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/inflect.py +0 -0
  160. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/ipython.py +0 -0
  161. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/iterables.py +0 -0
  162. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/jupyter.py +0 -0
  163. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/libcst.py +0 -0
  164. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/lightweight_charts.py +0 -0
  165. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/logging.py +0 -0
  166. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/luigi.py +0 -0
  167. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/math.py +0 -0
  168. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/memory_profiler.py +0 -0
  169. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/modules.py +0 -0
  170. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/more_itertools.py +0 -0
  171. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/numpy.py +0 -0
  172. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/operator.py +0 -0
  173. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/optuna.py +0 -0
  174. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/orjson.py +0 -0
  175. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/os.py +0 -0
  176. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/parse.py +0 -0
  177. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pathlib.py +0 -0
  178. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/period.py +0 -0
  179. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pickle.py +0 -0
  180. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/platform.py +0 -0
  181. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/polars.py +0 -0
  182. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/polars_ols.py +0 -0
  183. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pottery.py +0 -0
  184. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pqdm.py +0 -0
  185. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/psutil.py +0 -0
  186. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/py.typed +0 -0
  187. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pydantic.py +0 -0
  188. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pyinstrument.py +0 -0
  189. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pytest.py +0 -0
  190. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/pytest_regressions.py +0 -0
  191. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/python_dotenv.py +0 -0
  192. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/random.py +0 -0
  193. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/re.py +0 -0
  194. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/redis.py +0 -0
  195. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/reprlib.py +0 -0
  196. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/scipy.py +0 -0
  197. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/sentinel.py +0 -0
  198. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/shelve.py +0 -0
  199. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/slack_sdk.py +0 -0
  200. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/socket.py +0 -0
  201. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/sqlalchemy.py +0 -0
  202. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/sqlalchemy_polars.py +0 -0
  203. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/statsmodels.py +0 -0
  204. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/streamlit.py +0 -0
  205. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/string.py +0 -0
  206. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/tempfile.py +0 -0
  207. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/text.py +0 -0
  208. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/threading.py +0 -0
  209. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/timer.py +0 -0
  210. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/traceback.py +0 -0
  211. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/typing.py +0 -0
  212. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/tzdata.py +0 -0
  213. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/tzlocal.py +0 -0
  214. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/uuid.py +0 -0
  215. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/version.py +0 -0
  216. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/warnings.py +0 -0
  217. {dycw_utilities-0.132.4 → dycw_utilities-0.133.1}/src/utilities/zipfile.py +0 -0
  218. {dycw_utilities-0.132.4 → dycw_utilities-0.133.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.132.4
3
+ Version: 0.133.1
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -14,6 +14,7 @@ altair-test = [
14
14
  "img2pdf",
15
15
  "vl-convert-python",
16
16
  ]
17
+ arq = ["arq >= 0.26.3, < 0.27"]
17
18
  atools = ["atools >= 0.14.2, < 0.15"]
18
19
  cachetools = ["cachetools >= 5.5.2, < 5.6"]
19
20
  click = ["click >= 8.2.1, < 8.3"]
@@ -29,6 +30,7 @@ dataclasses-test = ["orjson", "polars-lts-cpu"]
29
30
  dev = [
30
31
  "coloredlogs >= 15.0.1, < 15.1",
31
32
  "coverage-conditional-plugin >= 0.9.0, < 0.10",
33
+ "dycw-pytest-only >= 2.1.1, < 2.2",
32
34
  "pyright[nodejs] >= 1.1.401, < 1.2",
33
35
  "pytest-cov >= 6.1.1, < 6.2",
34
36
  ]
@@ -74,7 +76,7 @@ pytest-regressions = ["pytest-regressions >= 2.8.0, < 2.9"]
74
76
  pytest-regressions-test = ["orjson", "polars-lts-cpu"]
75
77
  pytest-test = ["orjson", "pytest-rng", "pytest-rerunfailures"]
76
78
  python-dotenv = ["python-dotenv >= 1.1.0, < 1.2"]
77
- redis = ["redis >= 6.2.0, < 6.3", "orjson"]
79
+ redis = ["redis >= 5.3.0, < 6.0", "orjson"]
78
80
  redis-test = ["pytest-rerunfailures"]
79
81
  reprlib-test = ["rich"]
80
82
  scipy = ["scipy >= 1.15.3, < 1.16"]
@@ -102,7 +104,7 @@ dependencies = [
102
104
  name = "dycw-utilities"
103
105
  readme = "README.md"
104
106
  requires-python = ">= 3.12"
105
- version = "0.132.4"
107
+ version = "0.133.1"
106
108
 
107
109
  [project.optional-dependencies]
108
110
  logging = [
@@ -128,7 +130,7 @@ test = [
128
130
  # bump-my-version
129
131
  [tool.bumpversion]
130
132
  allow_dirty = true
131
- current_version = "0.132.4"
133
+ current_version = "0.133.1"
132
134
 
133
135
  [[tool.bumpversion.files]]
134
136
  filename = "src/utilities/__init__.py"
@@ -237,6 +239,7 @@ filterwarnings = [
237
239
  "error",
238
240
  "ignore:.*utcfromtimestamp.* is deprecated and scheduled for removal in a future version:DeprecationWarning", # luigi
239
241
  "ignore:Exception ignored in.* <coroutine object .* at .*>:pytest.PytestUnraisableExceptionWarning",
242
+ "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning",
240
243
  "ignore:Implicitly cleaning up <TemporaryDirectory '.*'>:ResourceWarning",
241
244
  "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning",
242
245
  "ignore:Task .* without outputs has no custom complete.* method:UserWarning", # luigi
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from asyncio import sleep
4
+ from typing import TYPE_CHECKING, Any, cast
5
+
6
+ from hypothesis import given
7
+ from hypothesis.strategies import integers
8
+
9
+ from utilities.arq import Worker, cron_raw
10
+ from utilities.iterables import one
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
15
+ from arq.cron import CronJob
16
+ from arq.typing import WorkerCoroutine
17
+
18
+ from utilities.types import CallableCoroutine1
19
+
20
+
21
+ class TestWorker:
22
+ @given(x=integers(), y=integers())
23
+ async def test_main(self, *, x: int, y: int) -> None:
24
+ async def func(x: int, y: int, /) -> int:
25
+ await sleep(0.01)
26
+ return x + y
27
+
28
+ class Example(Worker):
29
+ functions_raw: Sequence[CallableCoroutine1[Any]] = [func]
30
+
31
+ func_use = cast("WorkerCoroutine", one(Example.functions))
32
+ result = await func_use({}, x, y)
33
+ assert result == (x + y)
34
+
35
+ @given(x=integers(), y=integers())
36
+ async def test_cron(self, *, x: int, y: int) -> None:
37
+ async def func(x: int, y: int, /) -> int:
38
+ await sleep(0.01)
39
+ return x + y
40
+
41
+ class Example(Worker):
42
+ cron_jobs: Sequence[CronJob] | None = [cron_raw(func, args=(x, y))]
43
+
44
+ assert Example.cron_jobs is not None
45
+ cron_job = one(Example.cron_jobs)
46
+ result = await cron_job.coroutine({})
47
+ assert result == (x + y)
@@ -34,6 +34,7 @@ from utilities.click import (
34
34
  ExistingDirPath,
35
35
  ExistingFilePath,
36
36
  FilePath,
37
+ Freq,
37
38
  FrozenSetChoices,
38
39
  FrozenSetEnums,
39
40
  FrozenSetStrs,
@@ -51,6 +52,7 @@ from utilities.hypothesis import (
51
52
  date_deltas,
52
53
  date_time_deltas,
53
54
  dates,
55
+ freqs,
54
56
  months,
55
57
  pairs,
56
58
  plain_datetimes,
@@ -209,6 +211,7 @@ class TestParameters:
209
211
  attrgetter("name"),
210
212
  True,
211
213
  ),
214
+ param(Freq(), "FREQ", freqs(), utilities.whenever.Freq.serialize, True),
212
215
  param(
213
216
  FrozenSetChoices(["a", "b", "c"]),
214
217
  "FROZENSET[Choice(['a', 'b', 'c'])]",
@@ -41,6 +41,7 @@ from utilities.hypothesis import (
41
41
  Shape,
42
42
  _Draw2DefaultGeneratedSentinelError,
43
43
  _Draw2InputResolvedToSentinelError,
44
+ _freq_units,
44
45
  assume_does_not_raise,
45
46
  bool_arrays,
46
47
  date_deltas,
@@ -51,6 +52,7 @@ from utilities.hypothesis import (
51
52
  float64s,
52
53
  float_arrays,
53
54
  floats_extra,
55
+ freqs,
54
56
  git_repos,
55
57
  hashables,
56
58
  int32s,
@@ -107,6 +109,7 @@ from utilities.version import Version
107
109
  from utilities.whenever import (
108
110
  DATE_TWO_DIGIT_YEAR_MAX,
109
111
  DATE_TWO_DIGIT_YEAR_MIN,
112
+ Freq,
110
113
  Month,
111
114
  to_days,
112
115
  to_nanos,
@@ -118,7 +121,7 @@ if TYPE_CHECKING:
118
121
  from zoneinfo import ZoneInfo
119
122
 
120
123
  from utilities.tempfile import TemporaryDirectory
121
- from utilities.types import Number
124
+ from utilities.types import DateTimeRoundUnit, Number
122
125
 
123
126
 
124
127
  class TestAssumeDoesNotRaise:
@@ -486,6 +489,15 @@ class TestFloatsExtra:
486
489
  assert x == round(x)
487
490
 
488
491
 
492
+ class TestFreqs:
493
+ @given(data=data(), unit=_freq_units() | none())
494
+ def test_main(self, *, data: DataObject, unit: DateTimeRoundUnit | None) -> None:
495
+ freq = data.draw(freqs(unit=unit))
496
+ assert isinstance(freq, Freq)
497
+ if unit is not None:
498
+ assert freq.unit == unit
499
+
500
+
489
501
  class TestGitRepos:
490
502
  @given(data=data())
491
503
  @settings_with_reduced_examples()
@@ -23,6 +23,7 @@ from utilities.hypothesis import (
23
23
  date_deltas,
24
24
  date_time_deltas,
25
25
  dates,
26
+ freqs,
26
27
  plain_datetimes,
27
28
  temp_paths,
28
29
  text_ascii,
@@ -37,6 +38,7 @@ from utilities.typed_settings import (
37
38
  LoadSettingsError,
38
39
  load_settings,
39
40
  )
41
+ from utilities.whenever import Freq
40
42
 
41
43
  app_names = text_ascii(min_size=1).map(str.lower)
42
44
 
@@ -56,6 +58,7 @@ class TestExtendedTSConverter:
56
58
  date_time_deltas(parsable=True),
57
59
  DateTimeDelta.format_common_iso,
58
60
  ),
61
+ param(Freq, freqs(), Freq.serialize),
59
62
  param(IPv4Address, ip_addresses(v=4), IPv4Address),
60
63
  param(IPv6Address, ip_addresses(v=6), IPv6Address),
61
64
  param(PlainDateTime, plain_datetimes(), PlainDateTime.format_common_iso),
@@ -30,12 +30,15 @@ from utilities.hypothesis import (
30
30
  assume_does_not_raise,
31
31
  date_deltas,
32
32
  dates,
33
+ freqs,
33
34
  months,
34
35
  pairs,
35
36
  sentinels,
36
37
  zoned_datetimes,
37
38
  )
38
39
  from utilities.sentinel import Sentinel, sentinel
40
+ from utilities.types import DateTimeRoundUnit
41
+ from utilities.typing import get_literal_elements
39
42
  from utilities.tzdata import HongKong, Tokyo
40
43
  from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
41
44
  from utilities.whenever import (
@@ -64,12 +67,16 @@ from utilities.whenever import (
64
67
  ZERO_DAYS,
65
68
  ZONED_DATE_TIME_MAX,
66
69
  ZONED_DATE_TIME_MIN,
70
+ Freq,
67
71
  MeanDateTimeError,
68
72
  MinMaxDateError,
69
73
  Month,
70
74
  ToDaysError,
71
75
  ToNanosError,
72
76
  WheneverLogRecord,
77
+ _FreqDayIncrementError,
78
+ _FreqIncrementError,
79
+ _FreqParseError,
73
80
  _MinMaxDateMaxDateError,
74
81
  _MinMaxDateMinDateError,
75
82
  _MinMaxDatePeriodError,
@@ -128,6 +135,73 @@ class TestFormatCompact:
128
135
  assert parsed == expected
129
136
 
130
137
 
138
+ class TestFreq:
139
+ @given(freq=freqs())
140
+ def test_main(self, *, freq: Freq) -> None:
141
+ _ = get_now().round(unit=freq.unit, increment=freq.increment, mode="floor")
142
+
143
+ @given(unit=sampled_from(get_literal_elements(DateTimeRoundUnit)))
144
+ def test_abbreviate_and_expand(self, *, unit: DateTimeRoundUnit) -> None:
145
+ result = Freq._expand(Freq._abbreviate(unit))
146
+ assert result == unit
147
+
148
+ @given(freqs=pairs(freqs()))
149
+ def test_eq(self, *, freqs: tuple[Freq, Freq]) -> None:
150
+ x, y = freqs
151
+ result = x == y
152
+ assert isinstance(result, bool)
153
+
154
+ @given(freq=freqs())
155
+ def test_eq_non_freq(self, *, freq: Freq) -> None:
156
+ result = freq == 0
157
+ assert not result
158
+
159
+ @given(freq=freqs())
160
+ def test_hashable(self, *, freq: Freq) -> None:
161
+ _ = hash(freq)
162
+
163
+ @given(freq=freqs())
164
+ def test_repr(self, *, freq: Freq) -> None:
165
+ _ = repr(freq)
166
+
167
+ @given(freq=freqs())
168
+ def test_serialize_and_parse(self, *, freq: Freq) -> None:
169
+ result = Freq.parse(freq.serialize())
170
+ assert result == freq
171
+
172
+ def test_error_day(self) -> None:
173
+ with raises(
174
+ _FreqDayIncrementError,
175
+ match="Increment must be 1 for the 'day' unit; got 2",
176
+ ):
177
+ _ = Freq(unit="day", increment=2)
178
+
179
+ def test_error_hour(self) -> None:
180
+ with raises(
181
+ _FreqIncrementError,
182
+ match="Increment must be a proper divisor of 24 for the 'hour' unit; got 5",
183
+ ):
184
+ _ = Freq(unit="hour", increment=5)
185
+
186
+ def test_error_minute(self) -> None:
187
+ with raises(
188
+ _FreqIncrementError,
189
+ match="Increment must be a proper divisor of 60 for the 'minute' unit; got 7",
190
+ ):
191
+ _ = Freq(unit="minute", increment=7)
192
+
193
+ def test_error_milliseond(self) -> None:
194
+ with raises(
195
+ _FreqIncrementError,
196
+ match="Increment must be a proper divisor of 1000 for the 'millisecond' unit; got 3",
197
+ ):
198
+ _ = Freq(unit="millisecond", increment=3)
199
+
200
+ def test_error_parse(self) -> None:
201
+ with raises(_FreqParseError, match="Unable to parse frequency; got 's'"):
202
+ _ = Freq.parse("s")
203
+
204
+
131
205
  class TestFromTimeStamp:
132
206
  @given(
133
207
  datetime=zoned_datetimes(time_zone=timezones()).map(lambda d: d.round("second"))
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.132.4"
3
+ __version__ = "0.133.1"
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from functools import wraps
5
+ from itertools import chain
6
+ from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, cast, override
7
+
8
+ from arq.constants import default_queue_name, expires_extra_ms
9
+ from arq.cron import cron
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable, Iterable, Sequence
13
+ from datetime import timezone
14
+
15
+ from arq.connections import ArqRedis, RedisSettings
16
+ from arq.cron import CronJob
17
+ from arq.jobs import Deserializer, Serializer
18
+ from arq.typing import (
19
+ OptionType,
20
+ SecondsTimedelta,
21
+ StartupShutdown,
22
+ WeekdayOptionType,
23
+ WorkerCoroutine,
24
+ )
25
+ from arq.worker import Function
26
+
27
+ from utilities.types import CallableCoroutine1, Coroutine1, StrMapping
28
+
29
+ _P = ParamSpec("_P")
30
+ _T = TypeVar("_T")
31
+
32
+
33
+ ##
34
+
35
+
36
+ def cron_raw(
37
+ coroutine: CallableCoroutine1[Any],
38
+ /,
39
+ *,
40
+ name: str | None = None,
41
+ month: OptionType = None,
42
+ day: OptionType = None,
43
+ weekday: WeekdayOptionType = None,
44
+ hour: OptionType = None,
45
+ minute: OptionType = None,
46
+ second: OptionType = 0,
47
+ microsecond: int = 123_456,
48
+ run_at_startup: bool = False,
49
+ unique: bool = True,
50
+ job_id: str | None = None,
51
+ timeout: SecondsTimedelta | None = None,
52
+ keep_result: float | None = 0,
53
+ keep_result_forever: bool | None = False,
54
+ max_tries: int | None = 1,
55
+ args: Iterable[Any] | None = None,
56
+ kwargs: StrMapping | None = None,
57
+ ) -> CronJob:
58
+ """Create a cron job with a raw coroutine function."""
59
+ lifted = _lift_cron(
60
+ coroutine, *(() if args is None else args), **({} if kwargs is None else kwargs)
61
+ )
62
+ return cron(
63
+ lifted,
64
+ name=name,
65
+ month=month,
66
+ day=day,
67
+ weekday=weekday,
68
+ hour=hour,
69
+ minute=minute,
70
+ second=second,
71
+ microsecond=microsecond,
72
+ run_at_startup=run_at_startup,
73
+ unique=unique,
74
+ job_id=job_id,
75
+ timeout=timeout,
76
+ keep_result=keep_result,
77
+ keep_result_forever=keep_result_forever,
78
+ max_tries=max_tries,
79
+ )
80
+
81
+
82
+ def _lift_cron(
83
+ func: Callable[_P, Coroutine1[_T]], *args: _P.args, **kwargs: _P.kwargs
84
+ ) -> WorkerCoroutine:
85
+ """Lift a coroutine function & call arg/kwargs for `cron`."""
86
+
87
+ @wraps(func)
88
+ async def wrapped(ctx: StrMapping, /) -> _T:
89
+ _ = ctx
90
+ return await func(*args, **kwargs)
91
+
92
+ return cast("Any", wrapped)
93
+
94
+
95
+ ##
96
+
97
+
98
+ class _WorkerMeta(type):
99
+ @override
100
+ def __new__(
101
+ mcs: type[_WorkerMeta],
102
+ name: str,
103
+ bases: tuple[type, ...],
104
+ namespace: dict[str, Any],
105
+ /,
106
+ ) -> type[Worker]:
107
+ cls = cast("type[Worker]", super().__new__(mcs, name, bases, namespace))
108
+ cls.functions = tuple(chain(cls.functions, map(cls._lift, cls.functions_raw)))
109
+ return cls
110
+
111
+ @classmethod
112
+ def _lift(cls, func: Callable[_P, Coroutine1[_T]]) -> WorkerCoroutine:
113
+ """Lift a coroutine function to accept the required `ctx` argument."""
114
+
115
+ @wraps(func)
116
+ async def wrapped(ctx: StrMapping, *args: _P.args, **kwargs: _P.kwargs) -> _T:
117
+ _ = ctx
118
+ return await func(*args, **kwargs)
119
+
120
+ return cast("Any", wrapped)
121
+
122
+
123
+ @dataclass(kw_only=True)
124
+ class Worker(metaclass=_WorkerMeta):
125
+ """Base class for all workers."""
126
+
127
+ functions: Sequence[Function | WorkerCoroutine] = ()
128
+ functions_raw: Sequence[CallableCoroutine1[Any]] = ()
129
+ queue_name: str | None = default_queue_name
130
+ cron_jobs: Sequence[CronJob] | None = None
131
+ redis_settings: RedisSettings | None = None
132
+ redis_pool: ArqRedis | None = None
133
+ burst: bool = False
134
+ on_startup: StartupShutdown | None = None
135
+ on_shutdown: StartupShutdown | None = None
136
+ on_job_start: StartupShutdown | None = None
137
+ on_job_end: StartupShutdown | None = None
138
+ after_job_end: StartupShutdown | None = None
139
+ handle_signals: bool = True
140
+ job_completion_wait: int = 0
141
+ max_jobs: int = 10
142
+ job_timeout: SecondsTimedelta = 300
143
+ keep_result: SecondsTimedelta = 3600
144
+ keep_result_forever: bool = False
145
+ poll_delay: SecondsTimedelta = 0.5
146
+ queue_read_limit: int | None = None
147
+ max_tries: int = 5
148
+ health_check_interval: SecondsTimedelta = 3600
149
+ health_check_key: str | None = None
150
+ ctx: dict[Any, Any] | None = None
151
+ retry_jobs: bool = True
152
+ allow_abort_jobs: bool = False
153
+ max_burst_jobs: int = -1
154
+ job_serializer: Serializer | None = None
155
+ job_deserializer: Deserializer | None = None
156
+ expires_extra_ms: int = expires_extra_ms
157
+ timezone: timezone | None = None
158
+ log_results: bool = True
159
+
160
+
161
+ __all__ = ["Worker", "cron"]
@@ -29,7 +29,7 @@ from utilities.types import (
29
29
  TimeLike,
30
30
  ZonedDateTimeLike,
31
31
  )
32
- from utilities.whenever import _MonthParseCommonISOError
32
+ from utilities.whenever import FreqLike, _FreqParseError, _MonthParseCommonISOError
33
33
 
34
34
  if TYPE_CHECKING:
35
35
  from collections.abc import Iterable, Sequence
@@ -177,6 +177,30 @@ class Enum(ParamType, Generic[TEnum]):
177
177
  return _make_metavar(param, desc)
178
178
 
179
179
 
180
+ class Freq(ParamType):
181
+ """An frequency-valued parameter."""
182
+
183
+ @override
184
+ def __repr__(self) -> str:
185
+ return "FREQ"
186
+
187
+ @override
188
+ def convert(
189
+ self, value: FreqLike, param: Parameter | None, ctx: Context | None
190
+ ) -> utilities.whenever.Freq:
191
+ """Convert a value into the `Freq` type."""
192
+ match value:
193
+ case utilities.whenever.Freq():
194
+ return value
195
+ case str():
196
+ try:
197
+ return utilities.whenever.Freq.parse(value)
198
+ except _FreqParseError as error:
199
+ self.fail(str(error), param, ctx)
200
+ case _ as never:
201
+ assert_never(never)
202
+
203
+
180
204
  class IPv4Address(ParamType):
181
205
  """An IPv4 address-valued parameter."""
182
206
 
@@ -519,6 +543,7 @@ __all__ = [
519
543
  "ExistingDirPath",
520
544
  "ExistingFilePath",
521
545
  "FilePath",
546
+ "Freq",
522
547
  "FrozenSetChoices",
523
548
  "FrozenSetEnums",
524
549
  "FrozenSetParameter",
@@ -77,6 +77,8 @@ from utilities.pathlib import temp_cwd
77
77
  from utilities.platform import IS_WINDOWS
78
78
  from utilities.sentinel import Sentinel, sentinel
79
79
  from utilities.tempfile import TEMP_DIR, TemporaryDirectory
80
+ from utilities.types import DateTimeRoundUnit
81
+ from utilities.typing import get_literal_elements
80
82
  from utilities.version import Version
81
83
  from utilities.whenever import (
82
84
  DATE_DELTA_MAX,
@@ -100,6 +102,7 @@ from utilities.whenever import (
100
102
  TIME_DELTA_MIN,
101
103
  TIME_MAX,
102
104
  TIME_MIN,
105
+ Freq,
103
106
  Month,
104
107
  to_date_time_delta,
105
108
  to_days,
@@ -502,6 +505,38 @@ def floats_extra(
502
505
  ##
503
506
 
504
507
 
508
+ @composite
509
+ def freqs(
510
+ draw: DrawFn, /, *, unit: MaybeSearchStrategy[DateTimeRoundUnit | None] = None
511
+ ) -> Freq:
512
+ unit_ = draw2(draw, unit, _freq_units())
513
+ match unit_:
514
+ case "day":
515
+ return Freq(unit=unit_)
516
+ case "hour":
517
+ return Freq(unit=unit_, increment=draw(_freq_increments(24)))
518
+ case "minute" | "second":
519
+ return Freq(unit=unit_, increment=draw(_freq_increments(60)))
520
+ case "millisecond" | "microsecond" | "nanosecond":
521
+ return Freq(unit=unit_, increment=draw(_freq_increments(1000)))
522
+ case _ as never:
523
+ assert_never(never)
524
+
525
+
526
+ @composite
527
+ def _freq_units(draw: DrawFn, /) -> DateTimeRoundUnit:
528
+ return draw(sampled_from(get_literal_elements(DateTimeRoundUnit)))
529
+
530
+
531
+ @composite
532
+ def _freq_increments(draw: DrawFn, n: int, /) -> int:
533
+ divisors = [i for i in range(1, n) if n % i == 0]
534
+ return draw(sampled_from(divisors))
535
+
536
+
537
+ ##
538
+
539
+
505
540
  @composite
506
541
  def git_repos(draw: DrawFn, /) -> Path:
507
542
  path = draw(temp_paths())
@@ -1264,6 +1299,7 @@ __all__ = [
1264
1299
  "float64s",
1265
1300
  "float_arrays",
1266
1301
  "floats_extra",
1302
+ "freqs",
1267
1303
  "git_repos",
1268
1304
  "hashables",
1269
1305
  "int32s",
@@ -21,6 +21,7 @@ from whenever import (
21
21
  )
22
22
 
23
23
  from utilities.iterables import always_iterable
24
+ from utilities.whenever import Freq
24
25
 
25
26
  if TYPE_CHECKING:
26
27
  from collections.abc import Callable
@@ -52,6 +53,7 @@ class ExtendedTSConverter(TSConverter):
52
53
  (Date, Date.parse_common_iso),
53
54
  (DateDelta, DateDelta.parse_common_iso),
54
55
  (DateTimeDelta, DateTimeDelta.parse_common_iso),
56
+ (Freq, Freq.parse),
55
57
  (IPv4Address, IPv4Address),
56
58
  (IPv6Address, IPv6Address),
57
59
  (PlainDateTime, PlainDateTime.parse_common_iso),
@@ -72,12 +72,16 @@ type Coroutine1[_T] = Coroutine[Any, Any, _T]
72
72
  type MaybeAwaitable[_T] = _T | Awaitable[_T]
73
73
  type MaybeCallableEvent = MaybeCallable[Event]
74
74
  type MaybeCoroutine1[_T] = _T | Coroutine1[_T]
75
+ type CallableCoroutine1[_T] = Callable[..., Coroutine1[_T]]
75
76
 
76
77
 
77
78
  # callable
78
79
  TCallable = TypeVar("TCallable", bound=Callable[..., Any])
79
80
  TCallable1 = TypeVar("TCallable1", bound=Callable[..., Any])
80
81
  TCallable2 = TypeVar("TCallable2", bound=Callable[..., Any])
82
+ TCallableCoroutine1 = TypeVar(
83
+ "TCallableCoroutine1", bound=Callable[..., Coroutine1[Any]]
84
+ )
81
85
  TCallableMaybeCoroutine1None = TypeVar(
82
86
  "TCallableMaybeCoroutine1None", bound=Callable[..., MaybeCoroutine1[None]]
83
87
  )
@@ -292,6 +296,7 @@ type TimeZoneLike = (
292
296
 
293
297
 
294
298
  __all__ = [
299
+ "CallableCoroutine1",
295
300
  "Coroutine1",
296
301
  "Dataclass",
297
302
  "DateDeltaLike",
@@ -346,6 +351,7 @@ __all__ = [
346
351
  "TCallable",
347
352
  "TCallable1",
348
353
  "TCallable2",
354
+ "TCallableCoroutine1",
349
355
  "TCallableMaybeCoroutine1None",
350
356
  "TDataclass",
351
357
  "TEnum",