dycw-utilities 0.116.5__tar.gz → 0.117.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 (230) hide show
  1. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/PKG-INFO +3 -3
  2. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/pyproject.toml +5 -5
  3. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_asyncio.py +60 -1
  4. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_datetime.py +56 -56
  5. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/asyncio.py +44 -1
  7. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/datetime.py +58 -55
  8. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/whenever.py +2 -2
  9. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/.gitignore +0 -0
  10. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/LICENSE +0 -0
  11. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/README.md +0 -0
  12. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/__init__.py +0 -0
  13. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/conftest.py +0 -0
  14. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/__init__.py +0 -0
  15. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_missing/__init__.py +0 -0
  16. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_missing/module.py +0 -0
  17. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/__init__.py +0 -0
  18. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/outer_1.py +0 -0
  19. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/outer_2.py +0 -0
  20. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  21. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  22. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  23. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  24. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_without/__init__.py +0 -0
  25. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_without/module_1.py +0 -0
  26. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/package_without/module_2.py +0 -0
  27. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/standalone.py +0 -0
  28. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/modules/with_imports.py +0 -0
  29. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  30. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  31. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  32. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  33. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  34. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  35. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  36. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  37. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/__init__.py +0 -0
  38. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/test_async_service/__init__.py +0 -0
  39. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/test_async_service/__main__.py +0 -0
  40. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/test_async_service/run.sh +0 -0
  41. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  42. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  43. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  44. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_altair.py +0 -0
  45. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_astor.py +0 -0
  46. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_atomicwrites.py +0 -0
  47. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_atools.py +0 -0
  48. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_cachetools.py +0 -0
  49. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_click.py +0 -0
  50. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_concurrent.py +0 -0
  51. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_contextlib.py +0 -0
  52. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_contextvars.py +0 -0
  53. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_cryptography.py +0 -0
  54. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_cvxpy.py +0 -0
  55. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_dataclasses.py +0 -0
  56. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_enum.py +0 -0
  57. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_errors.py +0 -0
  58. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_eventkit.py +0 -0
  59. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_fastapi.py +0 -0
  60. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_fpdf2.py +0 -0
  61. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_functions.py +0 -0
  62. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_functools.py +0 -0
  63. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_getpass.py +0 -0
  64. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_git.py +0 -0
  65. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_hashlib.py +0 -0
  66. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_http.py +0 -0
  67. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_hypothesis.py +0 -0
  68. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_importlib.py +0 -0
  69. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_ipython.py +0 -0
  70. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_iterables.py +0 -0
  71. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_jupyter.py +0 -0
  72. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_lightweight_charts.py +0 -0
  73. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_logging.py +0 -0
  74. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_loguru.py +0 -0
  75. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_luigi.py +0 -0
  76. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_math.py +0 -0
  77. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_memory_profiler.py +0 -0
  78. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_modules.py +0 -0
  79. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_more_itertools.py +0 -0
  80. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_numpy.py +0 -0
  81. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_operator.py +0 -0
  82. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_optuna.py +0 -0
  83. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_orjson.py +0 -0
  84. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_os.py +0 -0
  85. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_parse.py +0 -0
  86. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pathlib.py +0 -0
  87. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_period.py +0 -0
  88. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pickle.py +0 -0
  89. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_platform.py +0 -0
  90. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_polars.py +0 -0
  91. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_polars_ols.py +0 -0
  92. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pqdm.py +0 -0
  93. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pydantic.py +0 -0
  94. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pyrsistent.py +0 -0
  96. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pytest.py +0 -0
  97. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_python_dotenv.py +0 -0
  99. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_random.py +0 -0
  100. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_re.py +0 -0
  101. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_redis.py +0 -0
  102. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_reprlib.py +0 -0
  103. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_rich.py +0 -0
  104. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_scipy.py +0 -0
  105. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_sentinel.py +0 -0
  106. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_shelve.py +0 -0
  107. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_slack_sdk.py +0 -0
  108. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_socket.py +0 -0
  109. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_sqlalchemy.py +0 -0
  110. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  111. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_statsmodel.py +0 -0
  112. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_streamlit.py +0 -0
  113. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_sys.py +0 -0
  114. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_tempfile.py +0 -0
  115. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_tenacity.py +0 -0
  116. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_text.py +0 -0
  117. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_threading.py +0 -0
  118. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_timer.py +0 -0
  119. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback.py +0 -0
  120. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
  121. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/chain.py +0 -0
  122. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  123. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  124. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  125. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/many.py +0 -0
  126. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/one.py +0 -0
  127. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
  128. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  129. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  130. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/two.py +0 -0
  131. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
  132. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_types.py +0 -0
  133. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_typing.py +0 -0
  134. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  135. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  136. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  137. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_tzdata.py +0 -0
  138. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_tzlocal.py +0 -0
  139. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_uuid.py +0 -0
  140. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_version.py +0 -0
  141. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_warnings.py +0 -0
  142. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_whenever.py +0 -0
  143. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_zipfile.py +0 -0
  144. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/tests/test_zoneinfo.py +0 -0
  145. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/altair.py +0 -0
  146. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/astor.py +0 -0
  147. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/atomicwrites.py +0 -0
  148. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/atools.py +0 -0
  149. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/cachetools.py +0 -0
  150. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/click.py +0 -0
  151. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/concurrent.py +0 -0
  152. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/contextlib.py +0 -0
  153. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/contextvars.py +0 -0
  154. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/cryptography.py +0 -0
  155. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/cvxpy.py +0 -0
  156. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/dataclasses.py +0 -0
  157. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/enum.py +0 -0
  158. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/errors.py +0 -0
  159. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/eventkit.py +0 -0
  160. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/fastapi.py +0 -0
  161. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/fpdf2.py +0 -0
  162. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/functions.py +0 -0
  163. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/functools.py +0 -0
  164. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/getpass.py +0 -0
  165. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/git.py +0 -0
  166. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/hashlib.py +0 -0
  167. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/http.py +0 -0
  168. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/hypothesis.py +0 -0
  169. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/importlib.py +0 -0
  170. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/ipython.py +0 -0
  171. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/iterables.py +0 -0
  172. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/jupyter.py +0 -0
  173. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/lightweight_charts.py +0 -0
  174. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/logging.py +0 -0
  175. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/loguru.py +0 -0
  176. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/luigi.py +0 -0
  177. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/math.py +0 -0
  178. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/memory_profiler.py +0 -0
  179. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/modules.py +0 -0
  180. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/more_itertools.py +0 -0
  181. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/numpy.py +0 -0
  182. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/operator.py +0 -0
  183. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/optuna.py +0 -0
  184. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/orjson.py +0 -0
  185. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/os.py +0 -0
  186. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/parse.py +0 -0
  187. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pathlib.py +0 -0
  188. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/period.py +0 -0
  189. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pickle.py +0 -0
  190. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/platform.py +0 -0
  191. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/polars.py +0 -0
  192. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/polars_ols.py +0 -0
  193. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pqdm.py +0 -0
  194. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/py.typed +0 -0
  195. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pydantic.py +0 -0
  196. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pyinstrument.py +0 -0
  197. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pyrsistent.py +0 -0
  198. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pytest.py +0 -0
  199. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/pytest_regressions.py +0 -0
  200. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/python_dotenv.py +0 -0
  201. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/random.py +0 -0
  202. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/re.py +0 -0
  203. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/redis.py +0 -0
  204. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/reprlib.py +0 -0
  205. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/rich.py +0 -0
  206. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/scipy.py +0 -0
  207. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/sentinel.py +0 -0
  208. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/shelve.py +0 -0
  209. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/slack_sdk.py +0 -0
  210. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/socket.py +0 -0
  211. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/sqlalchemy.py +0 -0
  212. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/sqlalchemy_polars.py +0 -0
  213. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/statsmodels.py +0 -0
  214. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/streamlit.py +0 -0
  215. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/sys.py +0 -0
  216. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/tempfile.py +0 -0
  217. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/tenacity.py +0 -0
  218. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/text.py +0 -0
  219. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/threading.py +0 -0
  220. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/timer.py +0 -0
  221. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/traceback.py +0 -0
  222. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/types.py +0 -0
  223. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/typing.py +0 -0
  224. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/tzdata.py +0 -0
  225. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/tzlocal.py +0 -0
  226. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/uuid.py +0 -0
  227. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/version.py +0 -0
  228. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/warnings.py +0 -0
  229. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/zipfile.py +0 -0
  230. {dycw_utilities-0.116.5 → dycw_utilities-0.117.0}/src/utilities/zoneinfo.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.116.5
3
+ Version: 0.117.0
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.17; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.19; 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'
@@ -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.17; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.19; 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'
@@ -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.17, < 6.132",
29
+ "hypothesis >= 6.131.19, < 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",
@@ -92,11 +92,11 @@ dependencies = [
92
92
  name = "dycw-utilities"
93
93
  readme = "README.md"
94
94
  requires-python = ">= 3.12"
95
- version = "0.116.5"
95
+ version = "0.117.0"
96
96
 
97
97
  [project.optional-dependencies]
98
98
  test = [
99
- "hypothesis >= 6.131.17, < 6.132",
99
+ "hypothesis >= 6.131.19, < 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",
@@ -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.17, < 6.132",
177
+ "hypothesis >= 6.131.19, < 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",
@@ -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.116.5"
337
+ current_version = "0.117.0"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -48,11 +48,13 @@ from utilities.asyncio import (
48
48
  put_items,
49
49
  put_items_nowait,
50
50
  sleep_dur,
51
+ sleep_until,
52
+ sleep_until_rounded,
51
53
  stream_command,
52
54
  timeout_dur,
53
55
  )
54
56
  from utilities.dataclasses import replace_non_sentinel
55
- from utilities.datetime import MILLISECOND, datetime_duration_to_timedelta
57
+ from utilities.datetime import MILLISECOND, datetime_duration_to_timedelta, get_now
56
58
  from utilities.hypothesis import sentinels, text_ascii
57
59
  from utilities.iterables import one, unique_everseen
58
60
  from utilities.pytest import skipif_windows
@@ -602,6 +604,22 @@ class TestInfiniteQueueLooper:
602
604
  _ = tg.create_task(add_items())
603
605
  assert 15 <= len(looper.output) <= 20
604
606
 
607
+ @given(n=integers(1, 10))
608
+ def test_len_and_empty(self, *, n: int) -> None:
609
+ class Example(InfiniteQueueLooper[None, int]):
610
+ output: set[int] = field(default_factory=set)
611
+
612
+ @override
613
+ async def _process_items(self, *items: int) -> None:
614
+ self.output.update(items)
615
+
616
+ looper = Example(sleep_core=0.05)
617
+ assert len(looper) == 0
618
+ assert looper.empty()
619
+ looper.put_items_nowait(*range(n))
620
+ assert len(looper) == n
621
+ assert not looper.empty()
622
+
605
623
  async def test_no_items(self) -> None:
606
624
  @dataclass(kw_only=True)
607
625
  class Example(InfiniteQueueLooper[None, int]):
@@ -616,6 +634,34 @@ class TestInfiniteQueueLooper:
616
634
  async with timeout_dur(duration=0.5):
617
635
  _ = await looper()
618
636
 
637
+ async def test_run_until_empty(self) -> None:
638
+ @dataclass(kw_only=True)
639
+ class Example(InfiniteQueueLooper[None, int]):
640
+ output: set[int] = field(default_factory=set)
641
+
642
+ @override
643
+ async def _process_items(self, *items: int) -> None:
644
+ self.output.update(items)
645
+
646
+ looper = Example(sleep_core=0.5)
647
+
648
+ async def add_items() -> None:
649
+ for i in count():
650
+ looper.put_items_nowait(i)
651
+ await sleep(0.01)
652
+
653
+ with raises(ExceptionGroup): # noqa: PT012
654
+ async with EnhancedTaskGroup(timeout=1.0) as tg:
655
+ _ = tg.create_task(looper())
656
+ _ = tg.create_task(add_items())
657
+
658
+ tasks = len(looper)
659
+ assert tasks >= 1
660
+ await sleep(0.1)
661
+ assert len(looper) == tasks
662
+ await looper.run_until_empty()
663
+ assert looper.empty()
664
+
619
665
  @given(logger=just("logger") | none())
620
666
  async def test_error_process_items(self, *, logger: str | None) -> None:
621
667
  class CustomError(Exception): ...
@@ -907,6 +953,19 @@ class TestSleepDur:
907
953
  assert timer <= 0.01
908
954
 
909
955
 
956
+ class TestSleepUntil:
957
+ async def test_main(self) -> None:
958
+ now = get_now()
959
+ with Timer() as timer:
960
+ await sleep_until(now + 10 * MILLISECOND)
961
+ assert timer >= datetime_duration_to_timedelta(5 * MILLISECOND)
962
+
963
+
964
+ class TestSleepUntilRounded:
965
+ async def test_main(self) -> None:
966
+ await sleep_until_rounded(10 * MILLISECOND)
967
+
968
+
910
969
  class TestStreamCommand:
911
970
  @skipif_windows
912
971
  async def test_main(self) -> None:
@@ -88,6 +88,8 @@ from utilities.datetime import (
88
88
  date_to_datetime,
89
89
  date_to_month,
90
90
  datetime_duration_to_float,
91
+ datetime_duration_to_microseconds,
92
+ datetime_duration_to_milliseconds,
91
93
  datetime_duration_to_timedelta,
92
94
  datetime_utc,
93
95
  days_since_epoch,
@@ -128,8 +130,6 @@ from utilities.datetime import (
128
130
  serialize_month,
129
131
  sub_duration,
130
132
  timedelta_since_epoch,
131
- timedelta_to_microseconds,
132
- timedelta_to_milliseconds,
133
133
  yield_days,
134
134
  yield_weekdays,
135
135
  )
@@ -520,6 +520,60 @@ class TestDateTimeDurationToFloat:
520
520
  assert result == timedelta.total_seconds()
521
521
 
522
522
 
523
+ class TestDateTimeDurationToMicrosecondsOrMilliseconds:
524
+ @given(timedelta=timedeltas())
525
+ def test_timedelta_to_microseconds(self, *, timedelta: dt.timedelta) -> None:
526
+ microseconds = datetime_duration_to_microseconds(timedelta)
527
+ result = microseconds_to_timedelta(microseconds)
528
+ assert result == timedelta
529
+
530
+ @given(microseconds=integers())
531
+ def test_microseconds_to_timedelta(self, *, microseconds: int) -> None:
532
+ with assume_does_not_raise(OverflowError):
533
+ timedelta = microseconds_to_timedelta(microseconds)
534
+ result = datetime_duration_to_microseconds(timedelta)
535
+ assert result == microseconds
536
+
537
+ @given(timedelta=timedeltas(), strict=booleans())
538
+ @settings(suppress_health_check={HealthCheck.filter_too_much})
539
+ def test_timedelta_to_milliseconds_exact(
540
+ self, *, timedelta: dt.timedelta, strict: bool
541
+ ) -> None:
542
+ _, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
543
+ _ = assume(remainder == 0)
544
+ milliseconds = datetime_duration_to_milliseconds(timedelta, strict=strict)
545
+ assert isinstance(milliseconds, int)
546
+ result = milliseconds_to_timedelta(milliseconds)
547
+ assert result == timedelta
548
+
549
+ @given(timedelta=timedeltas())
550
+ def test_timedelta_to_milliseconds_inexact(
551
+ self, *, timedelta: dt.timedelta
552
+ ) -> None:
553
+ _, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
554
+ _ = assume(remainder != 0)
555
+ milliseconds = datetime_duration_to_milliseconds(timedelta)
556
+ result = milliseconds_to_timedelta(round(milliseconds))
557
+ assert abs(result - timedelta) <= SECOND
558
+
559
+ @given(timedelta=timedeltas())
560
+ def test_timedelta_to_milliseconds_error(self, *, timedelta: dt.timedelta) -> None:
561
+ _, microseconds = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
562
+ _ = assume(microseconds != 0)
563
+ with raises(
564
+ TimedeltaToMillisecondsError,
565
+ match=r"Unable to convert .* to milliseconds; got .* microsecond\(s\)",
566
+ ):
567
+ _ = datetime_duration_to_milliseconds(timedelta, strict=True)
568
+
569
+ @given(milliseconds=int32s())
570
+ def test_milliseconds_to_timedelta(self, *, milliseconds: int) -> None:
571
+ with assume_does_not_raise(OverflowError):
572
+ timedelta = milliseconds_to_timedelta(milliseconds)
573
+ result = datetime_duration_to_milliseconds(timedelta)
574
+ assert result == milliseconds
575
+
576
+
523
577
  class TestDateTimeDurationToTimeDelta:
524
578
  @given(n=int32s())
525
579
  def test_int(self, *, n: int) -> None:
@@ -1205,60 +1259,6 @@ class TestTimedeltaSinceEpoch:
1205
1259
  assert result1 == result2
1206
1260
 
1207
1261
 
1208
- class TestTimedeltaToMicrosecondsOrMilliseconds:
1209
- @given(timedelta=timedeltas())
1210
- def test_timedelta_to_microseconds(self, *, timedelta: dt.timedelta) -> None:
1211
- microseconds = timedelta_to_microseconds(timedelta)
1212
- result = microseconds_to_timedelta(microseconds)
1213
- assert result == timedelta
1214
-
1215
- @given(microseconds=integers())
1216
- def test_microseconds_to_timedelta(self, *, microseconds: int) -> None:
1217
- with assume_does_not_raise(OverflowError):
1218
- timedelta = microseconds_to_timedelta(microseconds)
1219
- result = timedelta_to_microseconds(timedelta)
1220
- assert result == microseconds
1221
-
1222
- @given(timedelta=timedeltas(), strict=booleans())
1223
- @settings(suppress_health_check={HealthCheck.filter_too_much})
1224
- def test_timedelta_to_milliseconds_exact(
1225
- self, *, timedelta: dt.timedelta, strict: bool
1226
- ) -> None:
1227
- _, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
1228
- _ = assume(remainder == 0)
1229
- milliseconds = timedelta_to_milliseconds(timedelta, strict=strict)
1230
- assert isinstance(milliseconds, int)
1231
- result = milliseconds_to_timedelta(milliseconds)
1232
- assert result == timedelta
1233
-
1234
- @given(timedelta=timedeltas())
1235
- def test_timedelta_to_milliseconds_inexact(
1236
- self, *, timedelta: dt.timedelta
1237
- ) -> None:
1238
- _, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
1239
- _ = assume(remainder != 0)
1240
- milliseconds = timedelta_to_milliseconds(timedelta)
1241
- result = milliseconds_to_timedelta(round(milliseconds))
1242
- assert abs(result - timedelta) <= SECOND
1243
-
1244
- @given(timedelta=timedeltas())
1245
- def test_timedelta_to_milliseconds_error(self, *, timedelta: dt.timedelta) -> None:
1246
- _, microseconds = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
1247
- _ = assume(microseconds != 0)
1248
- with raises(
1249
- TimedeltaToMillisecondsError,
1250
- match=r"Unable to convert .* to milliseconds; got .* microsecond\(s\)",
1251
- ):
1252
- _ = timedelta_to_milliseconds(timedelta, strict=True)
1253
-
1254
- @given(milliseconds=int32s())
1255
- def test_milliseconds_to_timedelta(self, *, milliseconds: int) -> None:
1256
- with assume_does_not_raise(OverflowError):
1257
- timedelta = milliseconds_to_timedelta(milliseconds)
1258
- result = timedelta_to_milliseconds(timedelta)
1259
- assert result == milliseconds
1260
-
1261
-
1262
1262
  class TestTimedeltas:
1263
1263
  @mark.parametrize(
1264
1264
  "timedelta",
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.116.5"
3
+ __version__ = "0.117.0"
@@ -41,7 +41,14 @@ from typing import (
41
41
  override,
42
42
  )
43
43
 
44
- from utilities.datetime import MILLISECOND, MINUTE, SECOND, datetime_duration_to_float
44
+ from utilities.datetime import (
45
+ MILLISECOND,
46
+ MINUTE,
47
+ SECOND,
48
+ datetime_duration_to_float,
49
+ get_now,
50
+ round_datetime,
51
+ )
45
52
  from utilities.errors import ImpossibleCaseError, repr_error
46
53
  from utilities.functions import ensure_int, ensure_not_none, get_class_name
47
54
  from utilities.reprlib import get_repr
@@ -55,6 +62,7 @@ from utilities.types import (
55
62
  )
56
63
 
57
64
  if TYPE_CHECKING:
65
+ import datetime as dt
58
66
  from asyncio import _CoroutineLike
59
67
  from asyncio.subprocess import Process
60
68
  from collections.abc import AsyncIterator, Sequence
@@ -490,6 +498,9 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
490
498
  super().__post_init__()
491
499
  self._queue = self.queue_type()
492
500
 
501
+ def __len__(self) -> int:
502
+ return self._queue.qsize()
503
+
493
504
  @override
494
505
  async def _core(self) -> None:
495
506
  """Run the core part of the loop."""
@@ -505,10 +516,19 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
505
516
  async def _process_items(self, *items: _T) -> None:
506
517
  """Process the items."""
507
518
 
519
+ def empty(self) -> bool:
520
+ """Check if the queue is empty."""
521
+ return self._queue.empty()
522
+
508
523
  def put_items_nowait(self, *items: _T) -> None:
509
524
  """Put items into the queue."""
510
525
  put_items_nowait(items, self._queue)
511
526
 
527
+ async def run_until_empty(self) -> None:
528
+ """Run until the queue is empty."""
529
+ while not self.empty():
530
+ await self._process_items(*get_items_nowait(self._queue))
531
+
512
532
  @override
513
533
  def _error_upon_core(self, error: Exception, /) -> None:
514
534
  """Handle any errors upon running the core function."""
@@ -674,6 +694,27 @@ async def sleep_dur(*, duration: Duration | None = None) -> None:
674
694
  ##
675
695
 
676
696
 
697
+ async def sleep_until(datetime: dt.datetime, /) -> None:
698
+ """Sleep until a given time."""
699
+ await sleep_dur(duration=datetime - get_now())
700
+
701
+
702
+ ##
703
+
704
+
705
+ async def sleep_until_rounded(
706
+ duration: Duration, /, *, rel_tol: float | None = None, abs_tol: float | None = None
707
+ ) -> None:
708
+ """Sleep until a rounded time; accepts durations."""
709
+ datetime = round_datetime(
710
+ get_now(), duration, mode="ceil", rel_tol=rel_tol, abs_tol=abs_tol
711
+ )
712
+ await sleep_until(datetime)
713
+
714
+
715
+ ##
716
+
717
+
677
718
  @dataclass(kw_only=True, slots=True)
678
719
  class StreamCommandOutput:
679
720
  process: Process
@@ -756,6 +797,8 @@ __all__ = [
756
797
  "put_items",
757
798
  "put_items_nowait",
758
799
  "sleep_dur",
800
+ "sleep_until",
801
+ "sleep_until_rounded",
759
802
  "stream_command",
760
803
  "timeout_dur",
761
804
  ]
@@ -334,6 +334,52 @@ def datetime_duration_to_float(duration: Duration, /) -> float:
334
334
  assert_never(never)
335
335
 
336
336
 
337
+ def datetime_duration_to_microseconds(duration: Duration, /) -> int:
338
+ """Compute the number of microseconds in a datetime duration."""
339
+ timedelta = datetime_duration_to_timedelta(duration)
340
+ return (
341
+ _MICROSECONDS_PER_DAY * timedelta.days
342
+ + _MICROSECONDS_PER_SECOND * timedelta.seconds
343
+ + timedelta.microseconds
344
+ )
345
+
346
+
347
+ @overload
348
+ def datetime_duration_to_milliseconds(
349
+ duration: Duration, /, *, strict: Literal[True]
350
+ ) -> int: ...
351
+ @overload
352
+ def datetime_duration_to_milliseconds(
353
+ duration: Duration, /, *, strict: bool = False
354
+ ) -> float: ...
355
+ def datetime_duration_to_milliseconds(
356
+ duration: Duration, /, *, strict: bool = False
357
+ ) -> int | float:
358
+ """Compute the number of milliseconds in a datetime duration."""
359
+ timedelta = datetime_duration_to_timedelta(duration)
360
+ microseconds = datetime_duration_to_microseconds(timedelta)
361
+ milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
362
+ match remainder, strict:
363
+ case 0, _:
364
+ return milliseconds
365
+ case _, True:
366
+ raise TimedeltaToMillisecondsError(duration=duration, remainder=remainder)
367
+ case _, False:
368
+ return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
369
+ case _ as never:
370
+ assert_never(never)
371
+
372
+
373
+ @dataclass(kw_only=True, slots=True)
374
+ class TimedeltaToMillisecondsError(Exception):
375
+ duration: Duration
376
+ remainder: int
377
+
378
+ @override
379
+ def __str__(self) -> str:
380
+ return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)"
381
+
382
+
337
383
  def datetime_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
338
384
  """Ensure a datetime duration is a timedelta."""
339
385
  match duration:
@@ -651,8 +697,9 @@ YEAR = get_years(n=1)
651
697
  ##
652
698
 
653
699
 
654
- def is_integral_timedelta(timedelta: dt.timedelta, /) -> bool:
655
- """Check if a timedelta is integral."""
700
+ def is_integral_timedelta(duration: Duration, /) -> bool:
701
+ """Check if a duration is integral."""
702
+ timedelta = datetime_duration_to_timedelta(duration)
656
703
  return (timedelta.seconds == 0) and (timedelta.microseconds == 0)
657
704
 
658
705
 
@@ -679,9 +726,9 @@ def is_weekday(date: dt.date, /) -> bool:
679
726
  ##
680
727
 
681
728
 
682
- def is_zero_time(timedelta: dt.timedelta, /) -> bool:
729
+ def is_zero_time(duration: Duration, /) -> bool:
683
730
  """Check if a timedelta is 0."""
684
- return timedelta == ZERO_TIME
731
+ return datetime_duration_to_timedelta(duration) == ZERO_TIME
685
732
 
686
733
 
687
734
  ##
@@ -763,7 +810,7 @@ def mean_timedelta(
763
810
  case 1:
764
811
  return one(timedeltas)
765
812
  case _:
766
- microseconds = list(map(timedelta_to_microseconds, timedeltas))
813
+ microseconds = list(map(datetime_duration_to_microseconds, timedeltas))
767
814
  mean_float = fmean(microseconds, weights=weights)
768
815
  mean_int = round_(mean_float, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol)
769
816
  return microseconds_to_timedelta(mean_int)
@@ -781,7 +828,7 @@ class MeanTimeDeltaError(Exception):
781
828
 
782
829
  def microseconds_since_epoch(datetime: dt.datetime, /) -> int:
783
830
  """Compute the number of microseconds since the epoch."""
784
- return timedelta_to_microseconds(timedelta_since_epoch(datetime))
831
+ return datetime_duration_to_microseconds(timedelta_since_epoch(datetime))
785
832
 
786
833
 
787
834
  def microseconds_to_timedelta(microseconds: int, /) -> dt.timedelta:
@@ -980,7 +1027,7 @@ class _ParseTwoDigitYearInvalidStringError(Exception):
980
1027
 
981
1028
  def round_datetime(
982
1029
  datetime: dt.datetime,
983
- timedelta: dt.timedelta,
1030
+ duration: Duration,
984
1031
  /,
985
1032
  *,
986
1033
  mode: RoundMode = "standard",
@@ -990,7 +1037,7 @@ def round_datetime(
990
1037
  """Round a datetime to a timedelta."""
991
1038
  if datetime.tzinfo is None:
992
1039
  dividend = microseconds_since_epoch(datetime)
993
- divisor = timedelta_to_microseconds(timedelta)
1040
+ divisor = datetime_duration_to_microseconds(duration)
994
1041
  quotient, remainder = divmod(dividend, divisor)
995
1042
  rnd_remainder = round_(
996
1043
  remainder / divisor, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol
@@ -1000,7 +1047,7 @@ def round_datetime(
1000
1047
  return microseconds_since_epoch_to_datetime(microseconds)
1001
1048
  local = datetime.replace(tzinfo=None)
1002
1049
  rounded = round_datetime(
1003
- local, timedelta, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol
1050
+ local, duration, mode=mode, rel_tol=rel_tol, abs_tol=abs_tol
1004
1051
  )
1005
1052
  return rounded.replace(tzinfo=datetime.tzinfo)
1006
1053
 
@@ -1175,50 +1222,6 @@ def timedelta_since_epoch(date_or_datetime: DateOrDateTime, /) -> dt.timedelta:
1175
1222
  assert_never(never)
1176
1223
 
1177
1224
 
1178
- def timedelta_to_microseconds(timedelta: dt.timedelta, /) -> int:
1179
- """Compute the number of microseconds in a timedelta."""
1180
- return (
1181
- _MICROSECONDS_PER_DAY * timedelta.days
1182
- + _MICROSECONDS_PER_SECOND * timedelta.seconds
1183
- + timedelta.microseconds
1184
- )
1185
-
1186
-
1187
- @overload
1188
- def timedelta_to_milliseconds(
1189
- timedelta: dt.timedelta, /, *, strict: Literal[True]
1190
- ) -> int: ...
1191
- @overload
1192
- def timedelta_to_milliseconds(
1193
- timedelta: dt.timedelta, /, *, strict: bool = False
1194
- ) -> float: ...
1195
- def timedelta_to_milliseconds(
1196
- timedelta: dt.timedelta, /, *, strict: bool = False
1197
- ) -> int | float:
1198
- """Compute the number of milliseconds in a timedelta."""
1199
- microseconds = timedelta_to_microseconds(timedelta)
1200
- milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
1201
- match remainder, strict:
1202
- case 0, _:
1203
- return milliseconds
1204
- case _, True:
1205
- raise TimedeltaToMillisecondsError(timedelta=timedelta, remainder=remainder)
1206
- case _, False:
1207
- return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
1208
- case _ as never:
1209
- assert_never(never)
1210
-
1211
-
1212
- @dataclass(kw_only=True, slots=True)
1213
- class TimedeltaToMillisecondsError(Exception):
1214
- timedelta: dt.timedelta
1215
- remainder: int
1216
-
1217
- @override
1218
- def __str__(self) -> str:
1219
- return f"Unable to convert {self.timedelta} to milliseconds; got {self.remainder} microsecond(s)"
1220
-
1221
-
1222
1225
  ##
1223
1226
 
1224
1227
 
@@ -1367,6 +1370,8 @@ __all__ = [
1367
1370
  "date_to_datetime",
1368
1371
  "date_to_month",
1369
1372
  "datetime_duration_to_float",
1373
+ "datetime_duration_to_microseconds",
1374
+ "datetime_duration_to_milliseconds",
1370
1375
  "datetime_duration_to_timedelta",
1371
1376
  "datetime_utc",
1372
1377
  "days_since_epoch",
@@ -1407,8 +1412,6 @@ __all__ = [
1407
1412
  "serialize_month",
1408
1413
  "sub_duration",
1409
1414
  "timedelta_since_epoch",
1410
- "timedelta_to_microseconds",
1411
- "timedelta_to_milliseconds",
1412
1415
  "yield_days",
1413
1416
  "yield_weekdays",
1414
1417
  ]
@@ -14,8 +14,8 @@ from utilities.datetime import (
14
14
  _MICROSECONDS_PER_SECOND,
15
15
  ZERO_TIME,
16
16
  check_date_not_datetime,
17
+ datetime_duration_to_microseconds,
17
18
  parse_two_digit_year,
18
- timedelta_to_microseconds,
19
19
  )
20
20
  from utilities.math import ParseNumberError, parse_number
21
21
  from utilities.re import (
@@ -601,7 +601,7 @@ class SerializeZonedDateTimeError(Exception):
601
601
 
602
602
  def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
603
603
  """Serialize a timedelta."""
604
- total_microseconds = timedelta_to_microseconds(timedelta)
604
+ total_microseconds = datetime_duration_to_microseconds(timedelta)
605
605
  if total_microseconds == 0:
606
606
  return DateTimeDelta()
607
607
  if total_microseconds >= 1: