dycw-utilities 0.113.2__tar.gz → 0.113.4__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 (228) hide show
  1. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/PKG-INFO +7 -7
  2. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/pyproject.toml +13 -13
  3. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_click.py +7 -7
  4. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_datetime.py +82 -0
  5. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/click.py +6 -5
  7. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/datetime.py +98 -1
  8. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/redis.py +5 -5
  9. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/.gitignore +0 -0
  10. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/LICENSE +0 -0
  11. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/README.md +0 -0
  12. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/__init__.py +0 -0
  13. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/conftest.py +0 -0
  14. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/__init__.py +0 -0
  15. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_missing/__init__.py +0 -0
  16. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_missing/module.py +0 -0
  17. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/__init__.py +0 -0
  18. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/outer_1.py +0 -0
  19. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/outer_2.py +0 -0
  20. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  21. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  22. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  23. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  24. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_without/__init__.py +0 -0
  25. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_without/module_1.py +0 -0
  26. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/package_without/module_2.py +0 -0
  27. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/standalone.py +0 -0
  28. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/modules/with_imports.py +0 -0
  29. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  30. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  31. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  32. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  33. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  34. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  35. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  36. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  37. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/__init__.py +0 -0
  38. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/test_async_service/__init__.py +0 -0
  39. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/test_async_service/__main__.py +0 -0
  40. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/test_async_service/run.sh +0 -0
  41. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  42. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  43. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  44. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_altair.py +0 -0
  45. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_astor.py +0 -0
  46. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_asyncio.py +0 -0
  47. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_atomicwrites.py +0 -0
  48. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_atools.py +0 -0
  49. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_cachetools.py +0 -0
  50. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_concurrent.py +0 -0
  51. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_contextlib.py +0 -0
  52. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_contextvars.py +0 -0
  53. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_cryptography.py +0 -0
  54. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_cvxpy.py +0 -0
  55. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_dataclasses.py +0 -0
  56. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_enum.py +0 -0
  57. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_errors.py +0 -0
  58. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_eventkit.py +0 -0
  59. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_fastapi.py +0 -0
  60. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_fpdf2.py +0 -0
  61. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_functions.py +0 -0
  62. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_functools.py +0 -0
  63. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_getpass.py +0 -0
  64. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_git.py +0 -0
  65. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_hashlib.py +0 -0
  66. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_http.py +0 -0
  67. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_hypothesis.py +0 -0
  68. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_ipython.py +0 -0
  69. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_iterables.py +0 -0
  70. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_jupyter.py +0 -0
  71. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_lightweight_charts.py +0 -0
  72. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_logging.py +0 -0
  73. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_loguru.py +0 -0
  74. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_luigi.py +0 -0
  75. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_math.py +0 -0
  76. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_memory_profiler.py +0 -0
  77. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_modules.py +0 -0
  78. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_more_itertools.py +0 -0
  79. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_numpy.py +0 -0
  80. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_operator.py +0 -0
  81. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_optuna.py +0 -0
  82. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_orjson.py +0 -0
  83. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_os.py +0 -0
  84. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_parse.py +0 -0
  85. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pathlib.py +0 -0
  86. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_period.py +0 -0
  87. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pickle.py +0 -0
  88. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_platform.py +0 -0
  89. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_polars.py +0 -0
  90. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_polars_ols.py +0 -0
  91. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pqdm.py +0 -0
  92. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pydantic.py +0 -0
  93. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pyinstrument.py +0 -0
  94. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pyrsistent.py +0 -0
  95. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pytest.py +0 -0
  96. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_pytest_regressions.py +0 -0
  97. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_python_dotenv.py +0 -0
  98. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_random.py +0 -0
  99. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_re.py +0 -0
  100. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_redis.py +0 -0
  101. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_rich.py +0 -0
  103. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_scipy.py +0 -0
  104. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_sentinel.py +0 -0
  105. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_shelve.py +0 -0
  106. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_slack_sdk.py +0 -0
  107. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_socket.py +0 -0
  108. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_sqlalchemy.py +0 -0
  109. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_sqlalchemy_polars.py +0 -0
  110. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_statsmodel.py +0 -0
  111. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_streamlit.py +0 -0
  112. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_sys.py +0 -0
  113. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_tempfile.py +0 -0
  114. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_tenacity.py +0 -0
  115. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_text.py +0 -0
  116. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_threading.py +0 -0
  117. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_timer.py +0 -0
  118. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback.py +0 -0
  119. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/__init__.py +0 -0
  120. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/chain.py +0 -0
  121. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  122. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  123. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  124. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/many.py +0 -0
  125. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/one.py +0 -0
  126. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/recursive.py +0 -0
  127. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  128. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  129. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/two.py +0 -0
  130. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_traceback_funcs/untraced.py +0 -0
  131. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_types.py +0 -0
  132. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_typing.py +0 -0
  133. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_typing_funcs/__init__.py +0 -0
  134. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_typing_funcs/no_future.py +0 -0
  135. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_typing_funcs/with_future.py +0 -0
  136. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_tzdata.py +0 -0
  137. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_tzlocal.py +0 -0
  138. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_uuid.py +0 -0
  139. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_version.py +0 -0
  140. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_warnings.py +0 -0
  141. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_whenever.py +0 -0
  142. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_zipfile.py +0 -0
  143. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/tests/test_zoneinfo.py +0 -0
  144. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/altair.py +0 -0
  145. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/astor.py +0 -0
  146. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/asyncio.py +0 -0
  147. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/atomicwrites.py +0 -0
  148. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/atools.py +0 -0
  149. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/cachetools.py +0 -0
  150. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/concurrent.py +0 -0
  151. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/contextlib.py +0 -0
  152. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/contextvars.py +0 -0
  153. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/cryptography.py +0 -0
  154. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/cvxpy.py +0 -0
  155. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/dataclasses.py +0 -0
  156. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/enum.py +0 -0
  157. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/errors.py +0 -0
  158. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/eventkit.py +0 -0
  159. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/fastapi.py +0 -0
  160. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/fpdf2.py +0 -0
  161. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/functions.py +0 -0
  162. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/functools.py +0 -0
  163. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/getpass.py +0 -0
  164. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/git.py +0 -0
  165. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/hashlib.py +0 -0
  166. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/http.py +0 -0
  167. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/hypothesis.py +0 -0
  168. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/ipython.py +0 -0
  169. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/iterables.py +0 -0
  170. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/jupyter.py +0 -0
  171. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/lightweight_charts.py +0 -0
  172. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/logging.py +0 -0
  173. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/loguru.py +0 -0
  174. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/luigi.py +0 -0
  175. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/math.py +0 -0
  176. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/memory_profiler.py +0 -0
  177. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/modules.py +0 -0
  178. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/more_itertools.py +0 -0
  179. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/numpy.py +0 -0
  180. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/operator.py +0 -0
  181. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/optuna.py +0 -0
  182. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/orjson.py +0 -0
  183. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/os.py +0 -0
  184. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/parse.py +0 -0
  185. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pathlib.py +0 -0
  186. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/period.py +0 -0
  187. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pickle.py +0 -0
  188. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/platform.py +0 -0
  189. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/polars.py +0 -0
  190. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/polars_ols.py +0 -0
  191. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pqdm.py +0 -0
  192. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/py.typed +0 -0
  193. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pydantic.py +0 -0
  194. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pyinstrument.py +0 -0
  195. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pyrsistent.py +0 -0
  196. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pytest.py +0 -0
  197. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/pytest_regressions.py +0 -0
  198. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/python_dotenv.py +0 -0
  199. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/random.py +0 -0
  200. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/re.py +0 -0
  201. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/reprlib.py +0 -0
  202. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/rich.py +0 -0
  203. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/scipy.py +0 -0
  204. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/sentinel.py +0 -0
  205. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/shelve.py +0 -0
  206. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/slack_sdk.py +0 -0
  207. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/socket.py +0 -0
  208. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/sqlalchemy.py +0 -0
  209. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/sqlalchemy_polars.py +0 -0
  210. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/statsmodels.py +0 -0
  211. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/streamlit.py +0 -0
  212. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/sys.py +0 -0
  213. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/tempfile.py +0 -0
  214. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/tenacity.py +0 -0
  215. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/text.py +0 -0
  216. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/threading.py +0 -0
  217. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/timer.py +0 -0
  218. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/traceback.py +0 -0
  219. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/types.py +0 -0
  220. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/typing.py +0 -0
  221. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/tzdata.py +0 -0
  222. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/tzlocal.py +0 -0
  223. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/uuid.py +0 -0
  224. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/version.py +0 -0
  225. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/warnings.py +0 -0
  226. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/whenever.py +0 -0
  227. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.113.2 → dycw_utilities-0.113.4}/src/utilities/zoneinfo.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.113.2
3
+ Version: 0.113.4
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: typing-extensions<4.14,>=4.13.1
8
8
  Provides-Extra: test
9
- Requires-Dist: hypothesis<6.132,>=6.131.10; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.15; extra == 'test'
10
10
  Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
11
11
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
12
12
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -14,7 +14,7 @@ Requires-Dist: pytest-lazy-fixtures<1.2,>=1.1.2; extra == 'test'
14
14
  Requires-Dist: pytest-only<2.2,>=2.1.2; extra == 'test'
15
15
  Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
16
16
  Requires-Dist: pytest-regressions<2.8,>=2.7.0; extra == 'test'
17
- Requires-Dist: pytest-rerunfailures<16,>=15.0; extra == 'test'
17
+ Requires-Dist: pytest-rerunfailures<16,>=15.1; extra == 'test'
18
18
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
19
19
  Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
20
20
  Requires-Dist: pytest-xdist<3.7,>=3.6.1; extra == 'test'
@@ -37,7 +37,7 @@ Requires-Dist: atools<0.15,>=0.14.2; extra == 'zzz-test-atools'
37
37
  Provides-Extra: zzz-test-cachetools
38
38
  Requires-Dist: cachetools<5.6,>=5.5.2; extra == 'zzz-test-cachetools'
39
39
  Provides-Extra: zzz-test-click
40
- Requires-Dist: click<8.2,>=8.1.8; extra == 'zzz-test-click'
40
+ Requires-Dist: click<8.3,>=8.2.0; extra == 'zzz-test-click'
41
41
  Requires-Dist: sqlalchemy<2.1,>=2.0.40; extra == 'zzz-test-click'
42
42
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-click'
43
43
  Provides-Extra: zzz-test-contextlib
@@ -80,7 +80,7 @@ Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
81
81
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
83
- Requires-Dist: hypothesis<6.132,>=6.131.10; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.15; extra == 'zzz-test-hypothesis'
84
84
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
85
85
  Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
86
86
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
@@ -101,7 +101,7 @@ Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-jupyter'
101
101
  Provides-Extra: zzz-test-logging
102
102
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-logging'
103
103
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'zzz-test-logging'
104
- Requires-Dist: concurrent-log-handler<0.10,>=0.9.25; extra == 'zzz-test-logging'
104
+ Requires-Dist: concurrent-log-handler<0.10,>=0.9.26; extra == 'zzz-test-logging'
105
105
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-logging'
106
106
  Requires-Dist: tomlkit<0.14,>=0.13.2; extra == 'zzz-test-logging'
107
107
  Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-logging'
@@ -172,7 +172,7 @@ Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-redis'
172
172
  Provides-Extra: zzz-test-rich
173
173
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-rich'
174
174
  Provides-Extra: zzz-test-scipy
175
- Requires-Dist: scipy<1.16,>=1.15.2; extra == 'zzz-test-scipy'
175
+ Requires-Dist: scipy<1.16,>=1.15.3; extra == 'zzz-test-scipy'
176
176
  Provides-Extra: zzz-test-sentinel
177
177
  Provides-Extra: zzz-test-shelve
178
178
  Provides-Extra: zzz-test-slack-sdk
@@ -16,9 +16,9 @@ dev = [
16
16
  "atomicwrites >= 1.4.1, < 1.5",
17
17
  "atools >= 0.14.2, < 0.15",
18
18
  "cachetools >= 5.5.2, < 5.6",
19
- "click >= 8.1.8, < 8.2",
19
+ "click >= 8.2.0, < 8.3",
20
20
  "coloredlogs >= 15.0.1, < 15.1",
21
- "concurrent-log-handler >= 0.9.25, < 0.10",
21
+ "concurrent-log-handler >= 0.9.26, < 0.10",
22
22
  "cryptography >= 44.0.2, < 44.1",
23
23
  "cvxpy >= 1.6.5, < 1.7",
24
24
  "eventkit >= 1.0.3, < 1.1",
@@ -26,7 +26,7 @@ dev = [
26
26
  "fpdf2 >= 2.8.3, < 2.9",
27
27
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
28
28
  "httpx >= 0.28.1, < 0.29", # for fastapi
29
- "hypothesis >= 6.131.10, < 6.132",
29
+ "hypothesis >= 6.131.15, < 6.132",
30
30
  "img2pdf >= 0.6.0, < 0.7",
31
31
  "lightweight-charts >= 2.1, < 2.2",
32
32
  "loguru >= 0.7.3, < 0.8",
@@ -52,7 +52,7 @@ dev = [
52
52
  "redis >= 6.0.0, < 6.1",
53
53
  "rich >= 14.0.0, < 14.1",
54
54
  "scikit-learn >= 1.6.1, < 1.7",
55
- "scipy >= 1.15.2, < 1.16",
55
+ "scipy >= 1.15.3, < 1.16",
56
56
  "slack-sdk >= 3.35.0, < 3.36",
57
57
  "sqlalchemy >= 2.0.40, < 2.1",
58
58
  "statsmodels >= 0.14.4, < 0.15",
@@ -74,7 +74,7 @@ dev = [
74
74
  "pytest-lazy-fixtures >= 1.1.2, < 1.2",
75
75
  "pytest-only >= 2.1.2, < 2.2",
76
76
  "pytest-randomly >= 3.16.0, < 3.17",
77
- "pytest-rerunfailures >= 15.0, < 16",
77
+ "pytest-rerunfailures >= 15.1, < 16",
78
78
  "pytest-rng >= 1.0.0, < 1.1",
79
79
  "pytest-timeout >= 2.4.0, < 2.5",
80
80
  "pytest-xdist >= 3.6.1, < 3.7",
@@ -92,11 +92,11 @@ dependencies = [
92
92
  name = "dycw-utilities"
93
93
  readme = "README.md"
94
94
  requires-python = ">= 3.12"
95
- version = "0.113.2"
95
+ version = "0.113.4"
96
96
 
97
97
  [project.optional-dependencies]
98
98
  test = [
99
- "hypothesis >= 6.131.10, < 6.132",
99
+ "hypothesis >= 6.131.15, < 6.132",
100
100
  "pytest >= 8.3.5, < 8.4",
101
101
  "pytest-asyncio >= 0.26.0, < 0.27",
102
102
  "pytest-cov >= 6.1.1, < 6.2",
@@ -105,7 +105,7 @@ test = [
105
105
  "pytest-only >= 2.1.2, < 2.2",
106
106
  "pytest-randomly >= 3.16.0, < 3.17",
107
107
  "pytest-regressions >= 2.7.0, < 2.8",
108
- "pytest-rerunfailures >= 15.0, < 16",
108
+ "pytest-rerunfailures >= 15.1, < 16",
109
109
  "pytest-rng >= 1.0.0, < 1.1",
110
110
  "pytest-timeout >= 2.4.0, < 2.5",
111
111
  "pytest-xdist >= 3.6.1, < 3.7",
@@ -127,7 +127,7 @@ zzz-test-atools = [
127
127
  ]
128
128
  zzz-test-cachetools = ["cachetools >= 5.5.2, < 5.6"]
129
129
  zzz-test-click = [
130
- "click >= 8.1.8, < 8.2",
130
+ "click >= 8.2.0, < 8.3",
131
131
  "sqlalchemy >= 2.0.40, < 2.1",
132
132
  "whenever >= 0.7.3, < 0.8",
133
133
  ]
@@ -174,7 +174,7 @@ zzz-test-hypothesis = [
174
174
  "aiosqlite >= 0.21.0, < 0.22",
175
175
  "asyncpg >= 0.30.0, < 0.31", # for sqlalchemy async
176
176
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
177
- "hypothesis >= 6.131.10, < 6.132",
177
+ "hypothesis >= 6.131.15, < 6.132",
178
178
  "luigi >= 3.6.0, < 3.7",
179
179
  "numpy >= 2.2.5, < 2.3",
180
180
  "pathvalidate >= 3.2.3, < 3.3",
@@ -197,7 +197,7 @@ zzz-test-jupyter = [
197
197
  zzz-test-logging = [
198
198
  "atomicwrites >= 1.4.1, < 1.5",
199
199
  "coloredlogs >= 15.0.1, < 15.1",
200
- "concurrent-log-handler >= 0.9.25, < 0.10",
200
+ "concurrent-log-handler >= 0.9.26, < 0.10",
201
201
  "rich >= 14.0.0, < 14.1",
202
202
  "tomlkit >= 0.13.2, < 0.14",
203
203
  "tzlocal >= 5.3.1, < 5.4",
@@ -266,7 +266,7 @@ zzz-test-redis = [
266
266
  "whenever >= 0.7.3, < 0.8",
267
267
  ]
268
268
  zzz-test-rich = ["rich >= 14.0.0, < 14.1"]
269
- zzz-test-scipy = ["scipy >= 1.15.2, < 1.16"]
269
+ zzz-test-scipy = ["scipy >= 1.15.3, < 1.16"]
270
270
  zzz-test-sentinel = []
271
271
  zzz-test-shelve = []
272
272
  zzz-test-slack-sdk = [
@@ -334,7 +334,7 @@ zzz-test-zoneinfo = [
334
334
  # bump-my-version
335
335
  [tool.bumpversion]
336
336
  allow_dirty = true
337
- current_version = "0.113.2"
337
+ current_version = "0.113.4"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -102,12 +102,12 @@ class TestFileAndDirPaths:
102
102
  file_path.touch()
103
103
  result = CliRunner().invoke(cli, [str(file_path)])
104
104
  assert result.exit_code == 2
105
- assert search("is a file", result.stdout)
105
+ assert search("is a file", result.stderr)
106
106
 
107
107
  non_existent = tmp_path.joinpath("non-existent")
108
108
  result = CliRunner().invoke(cli, [str(non_existent)])
109
109
  assert result.exit_code == 2
110
- assert search("does not exist", result.stdout)
110
+ assert search("does not exist", result.stderr)
111
111
 
112
112
  def test_existing_file_path(self, *, tmp_path: Path) -> None:
113
113
  @command()
@@ -119,7 +119,7 @@ class TestFileAndDirPaths:
119
119
 
120
120
  result = CliRunner().invoke(cli, [str(tmp_path)])
121
121
  assert result.exit_code == 2
122
- assert search("is a directory", result.stdout)
122
+ assert search("is a directory", result.stderr)
123
123
 
124
124
  file_path = tmp_path.joinpath("file.txt")
125
125
  file_path.touch()
@@ -129,7 +129,7 @@ class TestFileAndDirPaths:
129
129
  non_existent = tmp_path.joinpath("non-existent")
130
130
  result = CliRunner().invoke(cli, [str(non_existent)])
131
131
  assert result.exit_code == 2
132
- assert search("does not exist", result.stdout)
132
+ assert search("does not exist", result.stderr)
133
133
 
134
134
  def test_dir_path(self, *, tmp_path: Path) -> None:
135
135
  @command()
@@ -146,7 +146,7 @@ class TestFileAndDirPaths:
146
146
  file_path.touch()
147
147
  result = CliRunner().invoke(cli, [str(file_path)])
148
148
  assert result.exit_code == 2
149
- assert search("is a file", result.stdout)
149
+ assert search("is a file", result.stderr)
150
150
 
151
151
  non_existent = tmp_path.joinpath("non-existent")
152
152
  result = CliRunner().invoke(cli, [str(non_existent)])
@@ -162,7 +162,7 @@ class TestFileAndDirPaths:
162
162
 
163
163
  result = CliRunner().invoke(cli, [str(tmp_path)])
164
164
  assert result.exit_code == 2
165
- assert search("is a directory", result.stdout)
165
+ assert search("is a directory", result.stderr)
166
166
 
167
167
  file_path = tmp_path.joinpath("file.txt")
168
168
  file_path.touch()
@@ -394,7 +394,7 @@ class TestParameters:
394
394
  assert result.exit_code == 2
395
395
  assert search(
396
396
  "Invalid value for '--value': Object '0' of type 'int' must be a string",
397
- result.stdout,
397
+ result.stderr,
398
398
  )
399
399
 
400
400
 
@@ -50,6 +50,7 @@ from utilities.datetime import (
50
50
  AreEqualDateTimesError,
51
51
  CheckDateNotDateTimeError,
52
52
  EnsureMonthError,
53
+ GetMinMaxDateError,
53
54
  MeanDateTimeError,
54
55
  MeanTimeDeltaError,
55
56
  MillisecondsSinceEpochError,
@@ -67,6 +68,11 @@ from utilities.datetime import (
67
68
  _DateDurationToIntTimeDeltaError,
68
69
  _DateDurationToTimeDeltaFloatError,
69
70
  _DateDurationToTimeDeltaTimeDeltaError,
71
+ _GetMinMaxDateMaxAgeError,
72
+ _GetMinMaxDateMaxDateError,
73
+ _GetMinMaxDateMinAgeError,
74
+ _GetMinMaxDateMinDateError,
75
+ _GetMinMaxDatePeriodError,
70
76
  _ParseTwoDigitYearInvalidIntegerError,
71
77
  _ParseTwoDigitYearInvalidStringError,
72
78
  add_duration,
@@ -91,6 +97,7 @@ from utilities.datetime import (
91
97
  get_date,
92
98
  get_datetime,
93
99
  get_half_years,
100
+ get_min_max_date,
94
101
  get_months,
95
102
  get_now,
96
103
  get_quarters,
@@ -129,6 +136,7 @@ from utilities.datetime import (
129
136
  from utilities.functions import not_func
130
137
  from utilities.hypothesis import (
131
138
  assume_does_not_raise,
139
+ date_durations,
132
140
  int32s,
133
141
  months,
134
142
  pairs,
@@ -148,6 +156,7 @@ if TYPE_CHECKING:
148
156
  from utilities.sentinel import Sentinel
149
157
  from utilities.types import (
150
158
  DateOrDateTime,
159
+ Duration,
151
160
  MaybeCallableDate,
152
161
  MaybeCallableDateTime,
153
162
  Number,
@@ -675,6 +684,79 @@ class TestGetDateTime:
675
684
  assert get_datetime(datetime=lambda: datetime) == datetime
676
685
 
677
686
 
687
+ class TestGetMinMaxDate:
688
+ @given(
689
+ min_date=dates(max_value=TODAY_UTC) | none(),
690
+ max_date=dates(max_value=TODAY_UTC) | none(),
691
+ min_age=date_durations(min_int=0, min_timedelta=ZERO_TIME) | none(),
692
+ max_age=date_durations(min_int=0, min_timedelta=ZERO_TIME) | none(),
693
+ )
694
+ def test_main(
695
+ self,
696
+ *,
697
+ min_date: dt.date | None,
698
+ max_date: dt.date | None,
699
+ min_age: Duration | None,
700
+ max_age: Duration | None,
701
+ ) -> None:
702
+ with assume_does_not_raise(GetMinMaxDateError, OverflowError):
703
+ min_date_use, max_date_use = get_min_max_date(
704
+ min_date=min_date, max_date=max_date, min_age=min_age, max_age=max_age
705
+ )
706
+ if (min_date is None) and (max_age is None):
707
+ assert min_date_use is None
708
+ else:
709
+ assert min_date_use is not None
710
+ if (max_date is None) and (min_age is None):
711
+ assert max_date_use is None
712
+ else:
713
+ assert max_date_use is not None
714
+ if min_date_use is not None:
715
+ assert min_date_use <= get_today()
716
+ if max_date_use is not None:
717
+ assert max_date_use <= get_today()
718
+ if (min_date_use is not None) and (max_date_use is not None):
719
+ assert min_date_use <= max_date_use
720
+
721
+ @given(date=dates(min_value=TODAY_UTC + DAY))
722
+ def test_error_min_date(self, *, date: dt.date) -> None:
723
+ with raises(
724
+ _GetMinMaxDateMinDateError,
725
+ match="Min date must be at most today; got .* > .*",
726
+ ):
727
+ _ = get_min_max_date(min_date=date)
728
+
729
+ @given(duration=date_durations(max_int=-1, max_timedelta=-DAY))
730
+ def test_error_min_age(self, *, duration: Duration) -> None:
731
+ with raises(
732
+ _GetMinMaxDateMinAgeError, match="Min age must be non-negative; got .*"
733
+ ):
734
+ _ = get_min_max_date(min_age=duration)
735
+
736
+ @given(date=dates(min_value=TODAY_UTC + DAY))
737
+ def test_error_max_date(self, *, date: dt.date) -> None:
738
+ with raises(
739
+ _GetMinMaxDateMaxDateError,
740
+ match="Max date must be at most today; got .* > .*",
741
+ ):
742
+ _ = get_min_max_date(max_date=date)
743
+
744
+ @given(duration=date_durations(max_int=-1, max_timedelta=-DAY))
745
+ def test_error_max_age(self, *, duration: Duration) -> None:
746
+ with raises(
747
+ _GetMinMaxDateMaxAgeError, match="Max age must be non-negative; got .*"
748
+ ):
749
+ _ = get_min_max_date(max_age=duration)
750
+
751
+ @given(dates=pairs(dates(max_value=TODAY_UTC), unique=True, sorted=True))
752
+ def test_error_period(self, *, dates: tuple[dt.date, dt.date]) -> None:
753
+ with raises(
754
+ _GetMinMaxDatePeriodError,
755
+ match="Min date must be at most max date; got .* > .*",
756
+ ):
757
+ _ = get_min_max_date(min_date=dates[1], max_date=dates[0])
758
+
759
+
678
760
  class TestGetNow:
679
761
  @given(time_zone=timezones())
680
762
  def test_function(self, *, time_zone: ZoneInfo) -> None:
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.113.2"
3
+ __version__ = "0.113.4"
@@ -125,7 +125,8 @@ class Enum(ParamType, Generic[TEnum]):
125
125
  self.fail(str(error), param, ctx)
126
126
 
127
127
  @override
128
- def get_metavar(self, param: Parameter) -> str | None:
128
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
129
+ _ = ctx
129
130
  desc = ",".join(e.name for e in self._enum)
130
131
  return _make_metavar(param, desc)
131
132
 
@@ -273,8 +274,8 @@ class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
273
274
  return frozenset(self._param.convert(v, param, ctx) for v in values)
274
275
 
275
276
  @override
276
- def get_metavar(self, param: Parameter) -> str | None:
277
- if (metavar := self._param.get_metavar(param)) is None:
277
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
278
+ if (metavar := self._param.get_metavar(param, ctx)) is None:
278
279
  name = self.name.upper()
279
280
  else:
280
281
  name = f"FROZENSET{metavar}"
@@ -397,8 +398,8 @@ class ListParameter(ParamType, Generic[_TParam, _T]):
397
398
  return [self._param.convert(v, param, ctx) for v in values]
398
399
 
399
400
  @override
400
- def get_metavar(self, param: Parameter) -> str | None:
401
- if (metavar := self._param.get_metavar(param)) is None:
401
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
402
+ if (metavar := self._param.get_metavar(param, ctx)) is None:
402
403
  name = self.name.upper()
403
404
  else:
404
405
  name = f"LIST{metavar}"
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
- from collections.abc import Callable, Iterable
4
+ from collections.abc import Callable, Iterable, Sequence
5
5
  from dataclasses import dataclass, replace
6
6
  from re import search, sub
7
7
  from statistics import fmean
@@ -495,6 +495,101 @@ def get_half_years(*, n: int = 1) -> dt.timedelta:
495
495
 
496
496
  HALF_YEAR = get_half_years(n=1)
497
497
 
498
+ ##
499
+
500
+
501
+ def get_min_max_date(
502
+ *,
503
+ min_date: dt.date | None = None,
504
+ max_date: dt.date | None = None,
505
+ min_age: Duration | None = None,
506
+ max_age: Duration | None = None,
507
+ time_zone: TimeZoneLike = UTC,
508
+ ) -> tuple[dt.date | None, dt.date | None]:
509
+ """Get the min/max date given a combination of dates/ages."""
510
+ today = get_today(time_zone=time_zone)
511
+ min_parts: Sequence[dt.date] = []
512
+ if min_date is not None:
513
+ if min_date > today:
514
+ raise _GetMinMaxDateMinDateError(min_date=min_date, today=today)
515
+ min_parts.append(min_date)
516
+ if max_age is not None:
517
+ if date_duration_to_timedelta(max_age) < ZERO_TIME:
518
+ raise _GetMinMaxDateMaxAgeError(max_age=max_age)
519
+ min_parts.append(sub_duration(today, duration=max_age))
520
+ min_date_use = max(min_parts, default=None)
521
+ max_parts: Sequence[dt.date] = []
522
+ if max_date is not None:
523
+ if max_date > today:
524
+ raise _GetMinMaxDateMaxDateError(max_date=max_date, today=today)
525
+ max_parts.append(max_date)
526
+ if min_age is not None:
527
+ if date_duration_to_timedelta(min_age) < ZERO_TIME:
528
+ raise _GetMinMaxDateMinAgeError(min_age=min_age)
529
+ max_parts.append(sub_duration(today, duration=min_age))
530
+ max_date_use = min(max_parts, default=None)
531
+ if (
532
+ (min_date_use is not None)
533
+ and (max_date_use is not None)
534
+ and (min_date_use > max_date_use)
535
+ ):
536
+ raise _GetMinMaxDatePeriodError(min_date=min_date_use, max_date=max_date_use)
537
+ return min_date_use, max_date_use
538
+
539
+
540
+ @dataclass(kw_only=True, slots=True)
541
+ class GetMinMaxDateError(Exception): ...
542
+
543
+
544
+ @dataclass(kw_only=True, slots=True)
545
+ class _GetMinMaxDateMinDateError(GetMinMaxDateError):
546
+ min_date: dt.date
547
+ today: dt.date
548
+
549
+ @override
550
+ def __str__(self) -> str:
551
+ return f"Min date must be at most today; got {self.min_date} > {self.today}"
552
+
553
+
554
+ @dataclass(kw_only=True, slots=True)
555
+ class _GetMinMaxDateMinAgeError(GetMinMaxDateError):
556
+ min_age: Duration
557
+
558
+ @override
559
+ def __str__(self) -> str:
560
+ return f"Min age must be non-negative; got {self.min_age}"
561
+
562
+
563
+ @dataclass(kw_only=True, slots=True)
564
+ class _GetMinMaxDateMaxDateError(GetMinMaxDateError):
565
+ max_date: dt.date
566
+ today: dt.date
567
+
568
+ @override
569
+ def __str__(self) -> str:
570
+ return f"Max date must be at most today; got {self.max_date} > {self.today}"
571
+
572
+
573
+ @dataclass(kw_only=True, slots=True)
574
+ class _GetMinMaxDateMaxAgeError(GetMinMaxDateError):
575
+ max_age: Duration
576
+
577
+ @override
578
+ def __str__(self) -> str:
579
+ return f"Max age must be non-negative; got {self.max_age}"
580
+
581
+
582
+ @dataclass(kw_only=True, slots=True)
583
+ class _GetMinMaxDatePeriodError(GetMinMaxDateError):
584
+ min_date: dt.date
585
+ max_date: dt.date
586
+
587
+ @override
588
+ def __str__(self) -> str:
589
+ return (
590
+ f"Min date must be at most max date; got {self.min_date} > {self.max_date}"
591
+ )
592
+
498
593
 
499
594
  ##
500
595
 
@@ -1244,6 +1339,7 @@ __all__ = [
1244
1339
  "CheckDateNotDateTimeError",
1245
1340
  "DateOrMonth",
1246
1341
  "EnsureMonthError",
1342
+ "GetMinMaxDateError",
1247
1343
  "MeanDateTimeError",
1248
1344
  "MeanTimeDeltaError",
1249
1345
  "MillisecondsSinceEpochError",
@@ -1280,6 +1376,7 @@ __all__ = [
1280
1376
  "get_date",
1281
1377
  "get_datetime",
1282
1378
  "get_half_years",
1379
+ "get_min_max_date",
1283
1380
  "get_months",
1284
1381
  "get_now",
1285
1382
  "get_quarters",
@@ -607,7 +607,7 @@ class Publisher(QueueProcessor[tuple[str, EncodableT]]):
607
607
 
608
608
 
609
609
  _SUBSCRIBE_TIMEOUT: Duration = SECOND
610
- _SUBSCRIBE_SLEEP: Duration = 10 * MILLISECOND
610
+ _SUBSCRIBE_SLEEP: Duration = MILLISECOND
611
611
 
612
612
 
613
613
  @overload
@@ -617,8 +617,8 @@ def subscribe(
617
617
  /,
618
618
  *,
619
619
  deserializer: Callable[[bytes], _T],
620
- timeout: Duration | None = ...,
621
- sleep: Duration = ...,
620
+ timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
621
+ sleep: Duration = _SUBSCRIBE_SLEEP,
622
622
  ) -> AsyncIterator[_T]: ...
623
623
  @overload
624
624
  def subscribe(
@@ -627,8 +627,8 @@ def subscribe(
627
627
  /,
628
628
  *,
629
629
  deserializer: None = None,
630
- timeout: Duration | None = ...,
631
- sleep: Duration = ...,
630
+ timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
631
+ sleep: Duration = _SUBSCRIBE_SLEEP,
632
632
  ) -> AsyncIterator[bytes]: ...
633
633
  async def subscribe(
634
634
  pubsub: PubSub,