dycw-utilities 0.129.5__tar.gz → 0.129.7__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.129.5 → dycw_utilities-0.129.7}/PKG-INFO +3 -3
  2. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/pyproject.toml +6 -6
  3. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/conftest.py +12 -0
  4. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_logging.py +40 -42
  5. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/logging.py +17 -7
  7. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/traceback.py +39 -8
  8. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/.gitignore +0 -0
  9. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/LICENSE +0 -0
  10. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/README.md +0 -0
  11. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/__init__.py +0 -0
  12. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/__init__.py +0 -0
  13. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_missing/__init__.py +0 -0
  14. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_missing/module.py +0 -0
  15. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/__init__.py +0 -0
  16. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/outer_1.py +0 -0
  17. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/outer_2.py +0 -0
  18. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  19. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  20. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  21. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  22. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_without/__init__.py +0 -0
  23. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_without/module_1.py +0 -0
  24. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/package_without/module_2.py +0 -0
  25. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/standalone.py +0 -0
  26. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/modules/with_imports.py +0 -0
  27. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  28. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  29. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  30. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  31. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  32. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  33. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  34. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  35. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_altair.py +0 -0
  36. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_asyncio.py +0 -0
  37. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_asyncio_classes/__init__.py +0 -0
  38. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_asyncio_classes/loopers.py +0 -0
  39. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_asyncio_classes/redis.py +0 -0
  40. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_atomicwrites.py +0 -0
  41. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_atools.py +0 -0
  42. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_cachetools.py +0 -0
  43. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_click.py +0 -0
  44. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_concurrent.py +0 -0
  45. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_contextlib.py +0 -0
  46. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_contextvars.py +0 -0
  47. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_cryptography.py +0 -0
  48. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_cvxpy.py +0 -0
  49. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_dataclasses.py +0 -0
  50. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_datetime.py +0 -0
  51. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_enum.py +0 -0
  52. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_errors.py +0 -0
  53. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_eventkit.py +0 -0
  54. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_fastapi.py +0 -0
  55. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_fpdf2.py +0 -0
  56. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_functions.py +0 -0
  57. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_functools.py +0 -0
  58. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_getpass.py +0 -0
  59. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_hashlib.py +0 -0
  60. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_http.py +0 -0
  61. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_hypothesis.py +0 -0
  62. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_importlib.py +0 -0
  63. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_ipython.py +0 -0
  64. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_iterables.py +0 -0
  65. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_jupyter.py +0 -0
  66. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_libcst.py +0 -0
  67. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_lightweight_charts.py +0 -0
  68. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_loguru.py +0 -0
  69. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_luigi.py +0 -0
  70. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_math.py +0 -0
  71. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_memory_profiler.py +0 -0
  72. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_modules.py +0 -0
  73. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_more_itertools.py +0 -0
  74. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_numpy.py +0 -0
  75. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_operator.py +0 -0
  76. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_optuna.py +0 -0
  77. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_orjson.py +0 -0
  78. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_os.py +0 -0
  79. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_parse.py +0 -0
  80. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pathlib.py +0 -0
  81. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_period.py +0 -0
  82. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pickle.py +0 -0
  83. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_platform.py +0 -0
  84. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_polars.py +0 -0
  85. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_polars_ols.py +0 -0
  86. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pottery.py +0 -0
  87. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pqdm.py +0 -0
  88. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_psutil.py +0 -0
  89. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pydantic.py +0 -0
  90. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pyinstrument.py +0 -0
  91. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pyrsistent.py +0 -0
  92. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pytest.py +0 -0
  93. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_pytest_regressions.py +0 -0
  94. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_python_dotenv.py +0 -0
  95. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_random.py +0 -0
  96. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_re.py +0 -0
  97. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_redis.py +0 -0
  98. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_reprlib.py +0 -0
  99. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_scipy.py +0 -0
  100. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_sentinel.py +0 -0
  101. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_shelve.py +0 -0
  102. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_slack_sdk.py +0 -0
  103. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_socket.py +0 -0
  104. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_sqlalchemy.py +0 -0
  105. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_sqlalchemy_polars.py +0 -0
  106. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_statsmodel.py +0 -0
  107. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_streamlit.py +0 -0
  108. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_string.py +0 -0
  109. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_sys.py +0 -0
  110. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_tempfile.py +0 -0
  111. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_tenacity.py +0 -0
  112. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_text.py +0 -0
  113. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_threading.py +0 -0
  114. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_timer.py +0 -0
  115. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback.py +0 -0
  116. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/__init__.py +0 -0
  117. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/chain.py +0 -0
  118. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  119. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  120. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  121. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/many.py +0 -0
  122. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/one.py +0 -0
  123. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/recursive.py +0 -0
  124. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  125. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  126. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/two.py +0 -0
  127. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_traceback_funcs/untraced.py +0 -0
  128. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_types.py +0 -0
  129. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_typing.py +0 -0
  130. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_typing_funcs/__init__.py +0 -0
  131. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_typing_funcs/no_future.py +0 -0
  132. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_typing_funcs/with_future.py +0 -0
  133. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_tzdata.py +0 -0
  134. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_tzlocal.py +0 -0
  135. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_uuid.py +0 -0
  136. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_version.py +0 -0
  137. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_warnings.py +0 -0
  138. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_whenever.py +0 -0
  139. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_zipfile.py +0 -0
  140. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/tests/test_zoneinfo.py +0 -0
  141. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/altair.py +0 -0
  142. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/asyncio.py +0 -0
  143. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/atomicwrites.py +0 -0
  144. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/atools.py +0 -0
  145. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/cachetools.py +0 -0
  146. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/click.py +0 -0
  147. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/concurrent.py +0 -0
  148. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/contextlib.py +0 -0
  149. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/contextvars.py +0 -0
  150. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/cryptography.py +0 -0
  151. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/cvxpy.py +0 -0
  152. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/dataclasses.py +0 -0
  153. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/datetime.py +0 -0
  154. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/enum.py +0 -0
  155. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/errors.py +0 -0
  156. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/eventkit.py +0 -0
  157. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/fastapi.py +0 -0
  158. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/fpdf2.py +0 -0
  159. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/functions.py +0 -0
  160. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/functools.py +0 -0
  161. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/getpass.py +0 -0
  162. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/hashlib.py +0 -0
  163. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/http.py +0 -0
  164. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/hypothesis.py +0 -0
  165. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/importlib.py +0 -0
  166. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/ipython.py +0 -0
  167. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/iterables.py +0 -0
  168. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/jupyter.py +0 -0
  169. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/libcst.py +0 -0
  170. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/lightweight_charts.py +0 -0
  171. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/loguru.py +0 -0
  172. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/luigi.py +0 -0
  173. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/math.py +0 -0
  174. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/memory_profiler.py +0 -0
  175. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/modules.py +0 -0
  176. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/more_itertools.py +0 -0
  177. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/numpy.py +0 -0
  178. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/operator.py +0 -0
  179. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/optuna.py +0 -0
  180. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/orjson.py +0 -0
  181. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/os.py +0 -0
  182. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/parse.py +0 -0
  183. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pathlib.py +0 -0
  184. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/period.py +0 -0
  185. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pickle.py +0 -0
  186. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/platform.py +0 -0
  187. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/polars.py +0 -0
  188. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/polars_ols.py +0 -0
  189. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pottery.py +0 -0
  190. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pqdm.py +0 -0
  191. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/psutil.py +0 -0
  192. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/py.typed +0 -0
  193. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pydantic.py +0 -0
  194. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pyinstrument.py +0 -0
  195. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pyrsistent.py +0 -0
  196. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pytest.py +0 -0
  197. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/pytest_regressions.py +0 -0
  198. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/python_dotenv.py +0 -0
  199. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/random.py +0 -0
  200. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/re.py +0 -0
  201. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/redis.py +0 -0
  202. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/reprlib.py +0 -0
  203. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/scipy.py +0 -0
  204. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/sentinel.py +0 -0
  205. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/shelve.py +0 -0
  206. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/slack_sdk.py +0 -0
  207. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/socket.py +0 -0
  208. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/sqlalchemy.py +0 -0
  209. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/sqlalchemy_polars.py +0 -0
  210. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/statsmodels.py +0 -0
  211. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/streamlit.py +0 -0
  212. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/string.py +0 -0
  213. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/sys.py +0 -0
  214. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/tempfile.py +0 -0
  215. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/tenacity.py +0 -0
  216. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/text.py +0 -0
  217. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/threading.py +0 -0
  218. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/timer.py +0 -0
  219. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/types.py +0 -0
  220. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/typing.py +0 -0
  221. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/tzdata.py +0 -0
  222. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/tzlocal.py +0 -0
  223. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/uuid.py +0 -0
  224. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/version.py +0 -0
  225. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/warnings.py +0 -0
  226. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/whenever.py +0 -0
  227. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.129.5 → dycw_utilities-0.129.7}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.129.5
3
+ Version: 0.129.7
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -93,7 +93,7 @@ Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-iterables'
93
93
  Requires-Dist: whenever<0.9,>=0.8.4; extra == 'zzz-test-iterables'
94
94
  Provides-Extra: zzz-test-jupyter
95
95
  Requires-Dist: jupyterlab<4.3,>=4.2.0; extra == 'zzz-test-jupyter'
96
- Requires-Dist: pandas<2.3,>=2.2.2; extra == 'zzz-test-jupyter'
96
+ Requires-Dist: pandas<2.4,>=2.3.0; extra == 'zzz-test-jupyter'
97
97
  Requires-Dist: polars-lts-cpu<1.31,>=1.30.0; extra == 'zzz-test-jupyter'
98
98
  Provides-Extra: zzz-test-logging
99
99
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-logging'
@@ -173,7 +173,7 @@ Requires-Dist: scipy<1.16,>=1.15.3; extra == 'zzz-test-scipy'
173
173
  Provides-Extra: zzz-test-sentinel
174
174
  Provides-Extra: zzz-test-shelve
175
175
  Provides-Extra: zzz-test-slack-sdk
176
- Requires-Dist: aiohttp<3.12.8,>=3.12.7; extra == 'zzz-test-slack-sdk'
176
+ Requires-Dist: aiohttp<3.12.10,>=3.12.9; extra == 'zzz-test-slack-sdk'
177
177
  Requires-Dist: slack-sdk<3.36,>=3.35.0; extra == 'zzz-test-slack-sdk'
178
178
  Provides-Extra: zzz-test-socket
179
179
  Provides-Extra: zzz-test-sqlalchemy
@@ -8,7 +8,7 @@ requires = ["hatchling"]
8
8
  # dependency groups
9
9
  [dependency-groups]
10
10
  dev = [
11
- "aiohttp >= 3.12.7, < 3.12.8", # for slack
11
+ "aiohttp >= 3.12.9, < 3.12.10", # for slack
12
12
  "aiosqlite >= 0.21.0, < 0.22",
13
13
  "altair >= 5.5.0, < 5.6",
14
14
  "asyncpg >= 0.30.0, < 0.31", # for sqlalchemy async
@@ -54,7 +54,7 @@ dev = [
54
54
  "python-dotenv >= 1.1.0, < 1.2",
55
55
  "redis >= 6.2.0, < 6.3",
56
56
  "rich >= 14.0.0, < 14.1",
57
- "scikit-learn >= 1.6.1, < 1.7",
57
+ "scikit-learn >= 1.7.0, < 1.8",
58
58
  "scipy >= 1.15.3, < 1.16",
59
59
  "slack-sdk >= 3.35.0, < 3.36",
60
60
  "sqlalchemy >= 2.0.41, < 2.1",
@@ -94,7 +94,7 @@ dependencies = [
94
94
  name = "dycw-utilities"
95
95
  readme = "README.md"
96
96
  requires-python = ">= 3.12"
97
- version = "0.129.5"
97
+ version = "0.129.7"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
@@ -191,7 +191,7 @@ zzz-test-iterables = [
191
191
  ]
192
192
  zzz-test-jupyter = [
193
193
  "jupyterlab >= 4.2.0, < 4.3",
194
- "pandas >= 2.2.2, < 2.3",
194
+ "pandas >= 2.3.0, < 2.4",
195
195
  "polars-lts-cpu >= 1.30.0, < 1.31",
196
196
  ]
197
197
  zzz-test-logging = [
@@ -270,7 +270,7 @@ 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 = [
273
- "aiohttp >= 3.12.7, < 3.12.8", # for slack
273
+ "aiohttp >= 3.12.9, < 3.12.10", # for slack
274
274
  "slack-sdk >= 3.35.0, < 3.36",
275
275
  ]
276
276
  zzz-test-socket = []
@@ -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.129.5"
337
+ current_version = "0.129.7"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -39,6 +39,8 @@ def traceback_func_chain() -> Pattern[str]:
39
39
  strip_and_dedent(
40
40
  r"""
41
41
  Date/time \| .+
42
+ Started \| .+
43
+ Duration \| .+
42
44
  User \| .+
43
45
  Host \| .+
44
46
  Version \|\s
@@ -98,6 +100,8 @@ def traceback_func_one() -> Pattern[str]:
98
100
  strip_and_dedent(
99
101
  r"""
100
102
  Date/time \| .+
103
+ Started \| .+
104
+ Duration \| .+
101
105
  User \| .+
102
106
  Host \| .+
103
107
  Version \|\s
@@ -134,6 +138,8 @@ def traceback_func_many_long() -> Pattern[str]:
134
138
  strip_and_dedent(
135
139
  r"""
136
140
  Date/time \| .+
141
+ Started \| .+
142
+ Duration \| .+
137
143
  User \| .+
138
144
  Host \| .+
139
145
  Version \|\s
@@ -192,6 +198,8 @@ def traceback_func_many_short() -> Pattern[str]:
192
198
  strip_and_dedent(
193
199
  r"""
194
200
  Date/time \| .+
201
+ Started \| .+
202
+ Duration \| .+
195
203
  User \| .+
196
204
  Host \| .+
197
205
  Version \|\s
@@ -228,6 +236,8 @@ def traceback_func_task_group_one() -> Pattern[str]:
228
236
  strip_and_dedent(
229
237
  r"""
230
238
  Date/time \| .+
239
+ Started \| .+
240
+ Duration \| .+
231
241
  User \| .+
232
242
  Host \| .+
233
243
  Version \|\s
@@ -287,6 +297,8 @@ def traceback_func_two() -> Pattern[str]:
287
297
  strip_and_dedent(
288
298
  r"""
289
299
  Date/time \| .+
300
+ Started \| .+
301
+ Duration \| .+
290
302
  User \| .+
291
303
  Host \| .+
292
304
  Version \|\s
@@ -42,6 +42,7 @@ from utilities.logging import (
42
42
  temp_logger,
43
43
  )
44
44
  from utilities.pytest import skipif_windows
45
+ from utilities.text import unique_str
45
46
  from utilities.types import LogLevel
46
47
  from utilities.typing import get_args
47
48
 
@@ -53,9 +54,9 @@ if TYPE_CHECKING:
53
54
 
54
55
 
55
56
  class TestAddFilters:
56
- @given(root=temp_paths(), expected=booleans())
57
- def test_main(self, *, root: Path, expected: bool) -> None:
58
- logger = getLogger(str(root))
57
+ @given(expected=booleans())
58
+ def test_main(self, *, expected: bool) -> None:
59
+ logger = getLogger(unique_str())
59
60
  logger.addHandler(handler := StreamHandler(buffer := StringIO()))
60
61
  add_filters(handler, lambda _: expected)
61
62
  assert len(handler.filters) == 1
@@ -71,10 +72,13 @@ class TestAddFilters:
71
72
 
72
73
 
73
74
  class TestBasicConfig:
74
- def test_main(self, *, tmp_path: Path) -> None:
75
- basic_config()
76
- logger = getLogger(str(tmp_path))
77
- logger.info("message")
75
+ @mark.parametrize("log", [param(True), param(False)])
76
+ def test_main(self, *, caplog: LogCaptureFixture, log: bool) -> None:
77
+ logger = unique_str() if log else None
78
+ basic_config(logger=logger)
79
+ logger_use = getLogger()
80
+ logger_use.warning("message")
81
+ assert "message" in caplog.messages
78
82
 
79
83
 
80
84
  class TestComputeRolloverActions:
@@ -191,16 +195,9 @@ class TestComputeRolloverActions:
191
195
 
192
196
 
193
197
  class TestFilterForKey:
194
- @given(
195
- root=temp_paths(),
196
- key=text_ascii(),
197
- value=booleans() | none(),
198
- default=booleans(),
199
- )
200
- def test_main(
201
- self, *, root: Path, key: str, value: bool | None, default: bool
202
- ) -> None:
203
- logger = getLogger(str(root))
198
+ @given(key=text_ascii(), value=booleans() | none(), default=booleans())
199
+ def test_main(self, *, key: str, value: bool | None, default: bool) -> None:
200
+ logger = getLogger(unique_str())
204
201
  logger.addHandler(handler := StreamHandler(buffer := StringIO()))
205
202
  with assume_does_not_raise(FilterForKeyError):
206
203
  filter_ = filter_for_key(key, default=default)
@@ -230,15 +227,16 @@ class TestGetDefaultLoggingPath:
230
227
 
231
228
 
232
229
  class TestGetLogger:
233
- def test_logger(self, *, tmp_path: Path) -> None:
234
- logger = getLogger(str(tmp_path))
230
+ def test_logger(self) -> None:
231
+ logger = getLogger(unique_str())
235
232
  result = get_logger(logger=logger)
236
233
  assert result is logger
237
234
 
238
- def test_str(self, *, tmp_path: Path) -> None:
239
- result = get_logger(logger=str(tmp_path))
240
- assert isinstance(result, Logger)
241
- assert result.name == str(tmp_path)
235
+ def test_str(self) -> None:
236
+ name = unique_str()
237
+ logger = getLogger(name)
238
+ assert isinstance(logger, Logger)
239
+ assert logger.name == name
242
240
 
243
241
  def test_none(self) -> None:
244
242
  result = get_logger()
@@ -382,7 +380,7 @@ class TestSetupLogging:
382
380
  def test_decorated(
383
381
  self, *, tmp_path: Path, traceback_func_one: Pattern[str]
384
382
  ) -> None:
385
- name = str(tmp_path)
383
+ name = unique_str()
386
384
  setup_logging(logger=name, files_dir=tmp_path)
387
385
  logger = getLogger(name)
388
386
  assert len(logger.handlers) == 7
@@ -397,7 +395,7 @@ class TestSetupLogging:
397
395
  def test_undecorated(
398
396
  self, *, tmp_path: Path, traceback_func_untraced: Pattern[str]
399
397
  ) -> None:
400
- name = str(tmp_path)
398
+ name = unique_str()
401
399
  setup_logging(logger=name, files_dir=tmp_path)
402
400
  logger = getLogger(name)
403
401
  assert len(logger.handlers) == 7
@@ -412,7 +410,7 @@ class TestSetupLogging:
412
410
  def test_regular_percent_formatting(
413
411
  self, *, tmp_path: Path, caplog: LogCaptureFixture
414
412
  ) -> None:
415
- name = str(tmp_path)
413
+ name = unique_str()
416
414
  setup_logging(logger=name, files_dir=tmp_path)
417
415
  logger = getLogger(name)
418
416
  logger.info("int: %d, float: %.2f", 1, 12.3456)
@@ -423,14 +421,14 @@ class TestSetupLogging:
423
421
 
424
422
  @skipif_windows
425
423
  def test_no_console(self, *, tmp_path: Path) -> None:
426
- name = str(tmp_path)
424
+ name = unique_str()
427
425
  setup_logging(logger=name, console_level=None, files_dir=tmp_path)
428
426
  logger = getLogger(name)
429
427
  assert len(logger.handlers) == 5
430
428
 
431
429
  @skipif_windows
432
430
  def test_zoned_datetime(self, *, tmp_path: Path, caplog: LogCaptureFixture) -> None:
433
- name = str(tmp_path)
431
+ name = unique_str()
434
432
  setup_logging(logger=name, files_dir=tmp_path)
435
433
  logger = getLogger(name)
436
434
  logger.info("")
@@ -441,7 +439,7 @@ class TestSetupLogging:
441
439
 
442
440
  @skipif_windows
443
441
  def test_extra(self, *, tmp_path: Path) -> None:
444
- name = str(tmp_path)
442
+ name = unique_str()
445
443
 
446
444
  def extra(logger: LoggerOrName | None, /) -> None:
447
445
  get_logger(logger=logger).addHandler(
@@ -478,7 +476,7 @@ class TestSetupLogging:
478
476
  class TestSizeAndTimeRotatingFileHandler:
479
477
  @skipif_windows
480
478
  def test_handlers(self, *, tmp_path: Path) -> None:
481
- logger = getLogger(str(tmp_path))
479
+ logger = getLogger(unique_str())
482
480
  filename = tmp_path.joinpath("log")
483
481
  logger.addHandler(SizeAndTimeRotatingFileHandler(filename=filename))
484
482
  logger.warning("message")
@@ -488,7 +486,7 @@ class TestSizeAndTimeRotatingFileHandler:
488
486
 
489
487
  @skipif_windows
490
488
  def test_size(self, *, tmp_path: Path) -> None:
491
- logger = getLogger(str(tmp_path))
489
+ logger = getLogger(unique_str())
492
490
  logger.addHandler(
493
491
  SizeAndTimeRotatingFileHandler(
494
492
  filename=tmp_path.joinpath("log.txt"), maxBytes=100, backupCount=3
@@ -547,7 +545,7 @@ class TestSizeAndTimeRotatingFileHandler:
547
545
  @mark.flaky
548
546
  @skipif_windows
549
547
  def test_time(self, *, tmp_path: Path) -> None:
550
- logger = getLogger(str(tmp_path))
548
+ logger = getLogger(unique_str())
551
549
  logger.addHandler(
552
550
  SizeAndTimeRotatingFileHandler(
553
551
  filename=tmp_path.joinpath("log.txt"),
@@ -620,7 +618,7 @@ class TestSizeAndTimeRotatingFileHandler:
620
618
  def test_should_rollover_file_not_found(
621
619
  self, *, tmp_path: Path, caplog: LogCaptureFixture
622
620
  ) -> None:
623
- logger = getLogger(str(tmp_path))
621
+ logger = getLogger(unique_str())
624
622
  path = tmp_path.joinpath("log")
625
623
  logger.addHandler(
626
624
  handler := SizeAndTimeRotatingFileHandler(filename=path, maxBytes=1)
@@ -634,7 +632,7 @@ class TestSizeAndTimeRotatingFileHandler:
634
632
  class TestStandaloneFileHandler:
635
633
  @skipif_windows
636
634
  def test_main(self, *, tmp_path: Path) -> None:
637
- logger = getLogger(str(tmp_path))
635
+ logger = getLogger(unique_str())
638
636
  logger.addHandler(StandaloneFileHandler(level=DEBUG, path=tmp_path))
639
637
  assert len(list(tmp_path.iterdir())) == 0
640
638
  logger.warning("message")
@@ -646,8 +644,8 @@ class TestStandaloneFileHandler:
646
644
 
647
645
 
648
646
  class TestTempHandler:
649
- def test_main(self, *, tmp_path: Path) -> None:
650
- logger = getLogger(str(tmp_path))
647
+ def test_main(self) -> None:
648
+ logger = getLogger(unique_str())
651
649
  logger.addHandler(h1 := StreamHandler())
652
650
  logger.addHandler(h2 := StreamHandler())
653
651
  assert len(logger.handlers) == 2
@@ -660,22 +658,22 @@ class TestTempHandler:
660
658
 
661
659
 
662
660
  class TestTempLogger:
663
- def test_disabled(self, *, tmp_path: Path) -> None:
664
- logger = getLogger(str(tmp_path))
661
+ def test_disabled(self) -> None:
662
+ logger = getLogger(unique_str())
665
663
  assert not logger.disabled
666
664
  with temp_logger(logger, disabled=True):
667
665
  assert logger.disabled
668
666
  assert not logger.disabled
669
667
 
670
- def test_level(self, *, tmp_path: Path) -> None:
671
- logger = getLogger(str(tmp_path))
668
+ def test_level(self) -> None:
669
+ logger = getLogger(unique_str())
672
670
  assert logger.level == NOTSET
673
671
  with temp_logger(logger, level="DEBUG"):
674
672
  assert logger.level == DEBUG
675
673
  assert logger.level == NOTSET
676
674
 
677
- def test_propagate(self, *, tmp_path: Path) -> None:
678
- logger = getLogger(str(tmp_path))
675
+ def test_propagate(self) -> None:
676
+ logger = getLogger(unique_str())
679
677
  assert logger.propagate
680
678
  with temp_logger(logger, propagate=False):
681
679
  assert not logger.propagate
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.129.5"
3
+ __version__ = "0.129.7"
@@ -415,16 +415,26 @@ def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
415
415
 
416
416
  def basic_config(
417
417
  *,
418
- format: str = "{asctime} | {name} | {levelname:8} | {message}", # noqa: A002
418
+ logger: LoggerOrName | None = None,
419
+ format_: str = "{asctime} | {name} | {levelname:8} | {message}",
419
420
  level: LogLevel = "INFO",
420
421
  ) -> None:
421
422
  """Do the basic config."""
422
- basicConfig(
423
- format=format,
424
- datefmt=maybe_sub_pct_y("%Y-%m-%d %H:%M:%S"),
425
- style="{",
426
- level=level,
427
- )
423
+ datefmt = maybe_sub_pct_y("%Y-%m-%d %H:%M:%S")
424
+ if logger is None:
425
+ basicConfig(format=format_, datefmt=datefmt, style="{", level=level)
426
+ else:
427
+ logger_use = get_logger(logger=logger)
428
+ logger_use.setLevel(level)
429
+ logger_use.addHandler(handler := StreamHandler())
430
+ handler.setLevel(level)
431
+ try:
432
+ from coloredlogs import ColoredFormatter
433
+ except ModuleNotFoundError: # pragma: no cover
434
+ formatter = Formatter(fmt=format_, datefmt=datefmt, style="{")
435
+ else:
436
+ formatter = ColoredFormatter(fmt=format_, datefmt=datefmt, style="{")
437
+ handler.setFormatter(formatter)
428
438
 
429
439
 
430
440
  ##
@@ -26,6 +26,7 @@ from typing import (
26
26
  runtime_checkable,
27
27
  )
28
28
 
29
+ from utilities.datetime import get_datetime, get_now
29
30
  from utilities.errors import ImpossibleCaseError
30
31
  from utilities.functions import (
31
32
  ensure_not_none,
@@ -45,8 +46,9 @@ from utilities.reprlib import (
45
46
  yield_call_args_repr,
46
47
  yield_mapping_repr,
47
48
  )
48
- from utilities.types import TBaseException, TCallable
49
+ from utilities.types import MaybeCallableDateTime, TBaseException, TCallable
49
50
  from utilities.version import get_version
51
+ from utilities.whenever import serialize_duration
50
52
 
51
53
  if TYPE_CHECKING:
52
54
  from collections.abc import Callable, Iterable, Iterator
@@ -60,6 +62,7 @@ if TYPE_CHECKING:
60
62
  _T = TypeVar("_T")
61
63
  _CALL_ARGS = "_CALL_ARGS"
62
64
  _INDENT = 4 * " "
65
+ _START = get_now()
63
66
 
64
67
 
65
68
  ##
@@ -78,6 +81,7 @@ class RichTracebackFormatter(Formatter):
78
81
  /,
79
82
  *,
80
83
  defaults: StrMapping | None = None,
84
+ start: MaybeCallableDateTime | None = _START,
81
85
  version: MaybeCallableVersionLike | None = None,
82
86
  max_width: int = RICH_MAX_WIDTH,
83
87
  indent_size: int = RICH_INDENT_SIZE,
@@ -89,7 +93,8 @@ class RichTracebackFormatter(Formatter):
89
93
  post: Callable[[str], str] | None = None,
90
94
  ) -> None:
91
95
  super().__init__(fmt, datefmt, style, validate, defaults=defaults)
92
- self._version = version
96
+ self._start = get_datetime(datetime=start)
97
+ self._version = get_version(version=version)
93
98
  self._max_width = max_width
94
99
  self._indent_size = indent_size
95
100
  self._max_length = max_length
@@ -110,6 +115,7 @@ class RichTracebackFormatter(Formatter):
110
115
  exc_value = ensure_not_none(exc_value, desc="exc_value")
111
116
  error = get_rich_traceback(
112
117
  exc_value,
118
+ start=self._start,
113
119
  version=self._version,
114
120
  max_width=self._max_width,
115
121
  indent_size=self._indent_size,
@@ -263,6 +269,7 @@ class ExcChainTB(Generic[TBaseException]):
263
269
  errors: list[
264
270
  ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException
265
271
  ] = field(default_factory=list)
272
+ start: MaybeCallableDateTime | None = field(default=_START, repr=False)
266
273
  version: MaybeCallableVersionLike | None = field(default=None, repr=False)
267
274
  max_width: int = RICH_MAX_WIDTH
268
275
  indent_size: int = RICH_INDENT_SIZE
@@ -292,7 +299,7 @@ class ExcChainTB(Generic[TBaseException]):
292
299
  """Format the traceback."""
293
300
  lines: list[str] = []
294
301
  if header: # pragma: no cover
295
- lines.extend(_yield_header_lines(version=self.version))
302
+ lines.extend(_yield_header_lines(start=self.start, version=self.version))
296
303
  total = len(self.errors)
297
304
  for i, errors in enumerate(self.errors, start=1):
298
305
  lines.append(f"Exception chain {i}/{total}:")
@@ -315,6 +322,7 @@ class ExcGroupTB(Generic[TBaseException]):
315
322
  errors: list[
316
323
  ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException
317
324
  ] = field(default_factory=list)
325
+ start: MaybeCallableDateTime | None = field(default=_START, repr=False)
318
326
  version: MaybeCallableVersionLike | None = field(default=None, repr=False)
319
327
  max_width: int = RICH_MAX_WIDTH
320
328
  indent_size: int = RICH_INDENT_SIZE
@@ -333,7 +341,7 @@ class ExcGroupTB(Generic[TBaseException]):
333
341
  """Format the traceback."""
334
342
  lines: list[str] = [] # skipif-ci
335
343
  if header: # pragma: no cover
336
- lines.extend(_yield_header_lines(version=self.version))
344
+ lines.extend(_yield_header_lines(start=self.start, version=self.version))
337
345
  lines.append("Exception group:") # skipif-ci
338
346
  match self.exc_group: # skipif-ci
339
347
  case ExcTB() as exc_tb:
@@ -363,6 +371,7 @@ class ExcTB(Generic[TBaseException]):
363
371
 
364
372
  frames: list[_Frame] = field(default_factory=list)
365
373
  error: TBaseException
374
+ start: MaybeCallableDateTime | None = field(default=_START, repr=False)
366
375
  version: MaybeCallableVersionLike | None = field(default=None, repr=False)
367
376
  max_width: int = RICH_MAX_WIDTH
368
377
  indent_size: int = RICH_INDENT_SIZE
@@ -391,7 +400,7 @@ class ExcTB(Generic[TBaseException]):
391
400
  total = len(self)
392
401
  lines: list[str] = []
393
402
  if header: # pragma: no cover
394
- lines.extend(_yield_header_lines(version=self.version))
403
+ lines.extend(_yield_header_lines(start=self.start, version=self.version))
395
404
  for i, frame in enumerate(self.frames):
396
405
  is_head = i < total - 1
397
406
  lines.append(
@@ -485,6 +494,7 @@ def get_rich_traceback(
485
494
  error: TBaseException,
486
495
  /,
487
496
  *,
497
+ start: MaybeCallableDateTime | None = _START,
488
498
  version: MaybeCallableVersionLike | None = None,
489
499
  max_width: int = RICH_MAX_WIDTH,
490
500
  indent_size: int = RICH_INDENT_SIZE,
@@ -506,6 +516,7 @@ def get_rich_traceback(
506
516
  err_recast = cast("TBaseException", err)
507
517
  return _get_rich_traceback_non_chain(
508
518
  err_recast,
519
+ start=start,
509
520
  version=version,
510
521
  max_width=max_width,
511
522
  indent_size=indent_size,
@@ -520,6 +531,7 @@ def get_rich_traceback(
520
531
  errors=[
521
532
  _get_rich_traceback_non_chain(
522
533
  e,
534
+ start=start,
523
535
  version=version,
524
536
  max_width=max_width,
525
537
  indent_size=indent_size,
@@ -530,6 +542,7 @@ def get_rich_traceback(
530
542
  )
531
543
  for e in errs_recast
532
544
  ],
545
+ start=start,
533
546
  version=version,
534
547
  max_width=max_width,
535
548
  indent_size=indent_size,
@@ -544,6 +557,7 @@ def _get_rich_traceback_non_chain(
544
557
  error: ExceptionGroup[Any] | TBaseException,
545
558
  /,
546
559
  *,
560
+ start: MaybeCallableDateTime | None = _START,
547
561
  version: MaybeCallableVersionLike | None = None,
548
562
  max_width: int = RICH_MAX_WIDTH,
549
563
  indent_size: int = RICH_INDENT_SIZE,
@@ -567,6 +581,7 @@ def _get_rich_traceback_non_chain(
567
581
  errors = [
568
582
  _get_rich_traceback_non_chain(
569
583
  e,
584
+ start=start,
570
585
  version=version,
571
586
  max_width=max_width,
572
587
  indent_size=indent_size,
@@ -580,6 +595,7 @@ def _get_rich_traceback_non_chain(
580
595
  return ExcGroupTB(
581
596
  exc_group=exc_group_or_exc_tb,
582
597
  errors=errors,
598
+ start=start,
583
599
  version=version,
584
600
  max_width=max_width,
585
601
  indent_size=indent_size,
@@ -591,6 +607,7 @@ def _get_rich_traceback_non_chain(
591
607
  case BaseException() as base_exc:
592
608
  return _get_rich_traceback_base_one(
593
609
  base_exc,
610
+ start=start,
594
611
  version=version,
595
612
  max_width=max_width,
596
613
  indent_size=indent_size,
@@ -607,6 +624,7 @@ def _get_rich_traceback_base_one(
607
624
  error: TBaseException,
608
625
  /,
609
626
  *,
627
+ start: MaybeCallableDateTime | None = _START,
610
628
  version: MaybeCallableVersionLike | None = None,
611
629
  max_width: int = RICH_MAX_WIDTH,
612
630
  indent_size: int = RICH_INDENT_SIZE,
@@ -638,6 +656,7 @@ def _get_rich_traceback_base_one(
638
656
  return ExcTB(
639
657
  frames=frames,
640
658
  error=error,
659
+ start=start,
641
660
  version=version,
642
661
  max_width=max_width,
643
662
  indent_size=indent_size,
@@ -793,13 +812,25 @@ def _merge_frames(
793
812
 
794
813
 
795
814
  def _yield_header_lines(
796
- *, version: MaybeCallableVersionLike | None = None
815
+ *,
816
+ start: MaybeCallableDateTime | None = _START,
817
+ version: MaybeCallableVersionLike | None = None,
797
818
  ) -> Iterator[str]:
798
819
  """Yield the header lines."""
799
- from utilities.tzlocal import get_now_local
820
+ from utilities.tzlocal import get_local_time_zone, get_now_local
800
821
  from utilities.whenever import serialize_zoned_datetime
801
822
 
802
- yield f"Date/time | {serialize_zoned_datetime(get_now_local())}"
823
+ now = get_now_local()
824
+ start_use = get_datetime(datetime=start)
825
+ start_use = (
826
+ None if start_use is None else start_use.astimezone(get_local_time_zone())
827
+ )
828
+ yield f"Date/time | {serialize_zoned_datetime(now)}"
829
+ start_str = "" if start_use is None else serialize_zoned_datetime(start_use)
830
+ yield f"Started | {start_str}"
831
+ duration = None if start_use is None else (now - start_use)
832
+ duration_str = "" if duration is None else serialize_duration(duration)
833
+ yield f"Duration | {duration_str}"
803
834
  yield f"User | {getuser()}"
804
835
  yield f"Host | {gethostname()}"
805
836
  version_use = "" if version is None else get_version(version=version)