dycw-utilities 0.127.0__tar.gz → 0.128.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 (232) hide show
  1. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/pyproject.toml +3 -2
  3. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio.py +40 -23
  4. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/loopers.py +2 -1
  5. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_hypothesis.py +1 -15
  6. dycw_utilities-0.128.0/src/tests/test_pathlib.py +124 -0
  7. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_python_dotenv.py +6 -6
  8. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/__init__.py +1 -1
  9. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/datetime.py +0 -8
  10. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/hypothesis.py +1 -11
  11. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/logging.py +9 -12
  12. dycw_utilities-0.128.0/src/utilities/pathlib.py +114 -0
  13. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pyinstrument.py +6 -4
  14. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pytest_regressions.py +2 -2
  15. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/python_dotenv.py +10 -6
  16. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/types.py +2 -2
  17. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/version.py +0 -8
  18. dycw_utilities-0.127.0/src/tests/test_git.py +0 -71
  19. dycw_utilities-0.127.0/src/tests/test_pathlib.py +0 -60
  20. dycw_utilities-0.127.0/src/utilities/git.py +0 -93
  21. dycw_utilities-0.127.0/src/utilities/pathlib.py +0 -55
  22. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/.gitignore +0 -0
  23. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/LICENSE +0 -0
  24. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/README.md +0 -0
  25. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/__init__.py +0 -0
  26. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/conftest.py +0 -0
  27. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/__init__.py +0 -0
  28. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_missing/__init__.py +0 -0
  29. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_missing/module.py +0 -0
  30. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/__init__.py +0 -0
  31. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/outer_1.py +0 -0
  32. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/outer_2.py +0 -0
  33. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  34. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  35. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  36. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  37. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_without/__init__.py +0 -0
  38. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_without/module_1.py +0 -0
  39. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_without/module_2.py +0 -0
  40. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/standalone.py +0 -0
  41. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/with_imports.py +0 -0
  42. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  43. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  44. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  45. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  46. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  47. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  48. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  49. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  50. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_altair.py +0 -0
  51. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/__init__.py +0 -0
  52. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/redis.py +0 -0
  53. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_atomicwrites.py +0 -0
  54. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_atools.py +0 -0
  55. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_cachetools.py +0 -0
  56. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_click.py +0 -0
  57. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_concurrent.py +0 -0
  58. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_contextlib.py +0 -0
  59. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_contextvars.py +0 -0
  60. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_cryptography.py +0 -0
  61. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_cvxpy.py +0 -0
  62. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_dataclasses.py +0 -0
  63. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_datetime.py +0 -0
  64. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_enum.py +0 -0
  65. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_errors.py +0 -0
  66. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_eventkit.py +0 -0
  67. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_fastapi.py +0 -0
  68. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_fpdf2.py +0 -0
  69. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_functions.py +0 -0
  70. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_functools.py +0 -0
  71. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_getpass.py +0 -0
  72. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_hashlib.py +0 -0
  73. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_http.py +0 -0
  74. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_importlib.py +0 -0
  75. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_ipython.py +0 -0
  76. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_iterables.py +0 -0
  77. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_jupyter.py +0 -0
  78. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_libcst.py +0 -0
  79. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_lightweight_charts.py +0 -0
  80. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_logging.py +0 -0
  81. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_loguru.py +0 -0
  82. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_luigi.py +0 -0
  83. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_math.py +0 -0
  84. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_memory_profiler.py +0 -0
  85. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_modules.py +0 -0
  86. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_more_itertools.py +0 -0
  87. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_numpy.py +0 -0
  88. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_operator.py +0 -0
  89. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_optuna.py +0 -0
  90. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_orjson.py +0 -0
  91. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_os.py +0 -0
  92. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_parse.py +0 -0
  93. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_period.py +0 -0
  94. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pickle.py +0 -0
  95. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_platform.py +0 -0
  96. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_polars.py +0 -0
  97. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_polars_ols.py +0 -0
  98. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pottery.py +0 -0
  99. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pqdm.py +0 -0
  100. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_psutil.py +0 -0
  101. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pydantic.py +0 -0
  102. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pyinstrument.py +0 -0
  103. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pyrsistent.py +0 -0
  104. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pytest.py +0 -0
  105. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pytest_regressions.py +0 -0
  106. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_random.py +0 -0
  107. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_re.py +0 -0
  108. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_redis.py +0 -0
  109. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_reprlib.py +0 -0
  110. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_scipy.py +0 -0
  111. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sentinel.py +0 -0
  112. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_shelve.py +0 -0
  113. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_slack_sdk.py +0 -0
  114. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_socket.py +0 -0
  115. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sqlalchemy.py +0 -0
  116. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  117. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_statsmodel.py +0 -0
  118. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_streamlit.py +0 -0
  119. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_string.py +0 -0
  120. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sys.py +0 -0
  121. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tempfile.py +0 -0
  122. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tenacity.py +0 -0
  123. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_text.py +0 -0
  124. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_threading.py +0 -0
  125. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_timer.py +0 -0
  126. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback.py +0 -0
  127. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
  128. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/chain.py +0 -0
  129. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  130. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  131. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  132. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/many.py +0 -0
  133. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/one.py +0 -0
  134. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
  135. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  136. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  137. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/two.py +0 -0
  138. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
  139. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_types.py +0 -0
  140. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing.py +0 -0
  141. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  142. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  143. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  144. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tzdata.py +0 -0
  145. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tzlocal.py +0 -0
  146. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_uuid.py +0 -0
  147. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_version.py +0 -0
  148. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_warnings.py +0 -0
  149. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_whenever.py +0 -0
  150. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_zipfile.py +0 -0
  151. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_zoneinfo.py +0 -0
  152. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/altair.py +0 -0
  153. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/asyncio.py +0 -0
  154. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/atomicwrites.py +0 -0
  155. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/atools.py +0 -0
  156. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/cachetools.py +0 -0
  157. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/click.py +0 -0
  158. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/concurrent.py +0 -0
  159. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/contextlib.py +0 -0
  160. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/contextvars.py +0 -0
  161. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/cryptography.py +0 -0
  162. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/cvxpy.py +0 -0
  163. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/dataclasses.py +0 -0
  164. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/enum.py +0 -0
  165. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/errors.py +0 -0
  166. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/eventkit.py +0 -0
  167. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/fastapi.py +0 -0
  168. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/fpdf2.py +0 -0
  169. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/functions.py +0 -0
  170. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/functools.py +0 -0
  171. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/getpass.py +0 -0
  172. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/hashlib.py +0 -0
  173. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/http.py +0 -0
  174. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/importlib.py +0 -0
  175. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/ipython.py +0 -0
  176. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/iterables.py +0 -0
  177. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/jupyter.py +0 -0
  178. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/libcst.py +0 -0
  179. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/lightweight_charts.py +0 -0
  180. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/loguru.py +0 -0
  181. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/luigi.py +0 -0
  182. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/math.py +0 -0
  183. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/memory_profiler.py +0 -0
  184. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/modules.py +0 -0
  185. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/more_itertools.py +0 -0
  186. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/numpy.py +0 -0
  187. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/operator.py +0 -0
  188. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/optuna.py +0 -0
  189. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/orjson.py +0 -0
  190. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/os.py +0 -0
  191. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/parse.py +0 -0
  192. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/period.py +0 -0
  193. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pickle.py +0 -0
  194. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/platform.py +0 -0
  195. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/polars.py +0 -0
  196. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/polars_ols.py +0 -0
  197. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pottery.py +0 -0
  198. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pqdm.py +0 -0
  199. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/psutil.py +0 -0
  200. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/py.typed +0 -0
  201. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pydantic.py +0 -0
  202. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pyrsistent.py +0 -0
  203. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pytest.py +0 -0
  204. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/random.py +0 -0
  205. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/re.py +0 -0
  206. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/redis.py +0 -0
  207. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/reprlib.py +0 -0
  208. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/scipy.py +0 -0
  209. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sentinel.py +0 -0
  210. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/shelve.py +0 -0
  211. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/slack_sdk.py +0 -0
  212. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/socket.py +0 -0
  213. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sqlalchemy.py +0 -0
  214. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sqlalchemy_polars.py +0 -0
  215. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/statsmodels.py +0 -0
  216. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/streamlit.py +0 -0
  217. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/string.py +0 -0
  218. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sys.py +0 -0
  219. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tempfile.py +0 -0
  220. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tenacity.py +0 -0
  221. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/text.py +0 -0
  222. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/threading.py +0 -0
  223. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/timer.py +0 -0
  224. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/traceback.py +0 -0
  225. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/typing.py +0 -0
  226. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tzdata.py +0 -0
  227. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tzlocal.py +0 -0
  228. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/uuid.py +0 -0
  229. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/warnings.py +0 -0
  230. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/whenever.py +0 -0
  231. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/zipfile.py +0 -0
  232. {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.127.0
3
+ Version: 0.128.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -94,7 +94,7 @@ dependencies = [
94
94
  name = "dycw-utilities"
95
95
  readme = "README.md"
96
96
  requires-python = ">= 3.12"
97
- version = "0.127.0"
97
+ version = "0.128.0"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
@@ -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.127.0"
337
+ current_version = "0.128.0"
338
338
 
339
339
  [[tool.bumpversion.files]]
340
340
  filename = "src/utilities/__init__.py"
@@ -447,6 +447,7 @@ filterwarnings = [
447
447
  "ignore:Task .* without outputs has no custom complete.* method:UserWarning", # luigi
448
448
  "ignore:There is no current event loop:DeprecationWarning", # eventkit
449
449
  "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
450
+ "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
450
451
  "ignore:loop is closed:ResourceWarning", # redis
451
452
  "ignore:unclosed <StreamWriter .*>:ResourceWarning", # redis
452
453
  "ignore:unclosed <socket.*socket .*>:ResourceWarning", # redis
@@ -15,7 +15,6 @@ from hypothesis.strategies import (
15
15
  booleans,
16
16
  data,
17
17
  integers,
18
- just,
19
18
  lists,
20
19
  none,
21
20
  permutations,
@@ -71,6 +70,7 @@ from utilities.hypothesis import sentinels, text_ascii
71
70
  from utilities.iterables import one, unique_everseen
72
71
  from utilities.pytest import skipif_windows
73
72
  from utilities.sentinel import Sentinel, sentinel
73
+ from utilities.text import unique_str
74
74
  from utilities.timer import Timer
75
75
 
76
76
  if TYPE_CHECKING:
@@ -198,7 +198,6 @@ class TestEnhancedTaskGroup:
198
198
  assert looper.running
199
199
  assert not looper.running
200
200
 
201
- @mark.flaky
202
201
  async def test_max_tasks_disabled(self) -> None:
203
202
  with Timer() as timer:
204
203
  async with EnhancedTaskGroup() as tg:
@@ -625,7 +624,7 @@ class TestInfiniteLooper:
625
624
  ):
626
625
  raise _InfiniteLooperDefaultEventError(looper=looper)
627
626
 
628
- @given(logger=just("logger") | none())
627
+ @given(log=booleans())
629
628
  @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
630
629
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
631
630
  async def test_error_upon_initialize(
@@ -633,7 +632,7 @@ class TestInfiniteLooper:
633
632
  *,
634
633
  sleep_restart: DurationOrEveryDuration,
635
634
  desc: str,
636
- logger: str | None,
635
+ log: bool,
637
636
  caplog: LogCaptureFixture,
638
637
  ) -> None:
639
638
  class CustomError(Exception): ...
@@ -650,15 +649,19 @@ class TestInfiniteLooper:
650
649
 
651
650
  async with (
652
651
  timeout(1.0),
653
- Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger),
652
+ Example(
653
+ sleep_core=0.1,
654
+ sleep_restart=sleep_restart,
655
+ logger=unique_str() if log else None,
656
+ ),
654
657
  ):
655
658
  ...
656
- if logger is not None:
659
+ if log:
657
660
  message = caplog.messages[0]
658
661
  expected = f"'Example' encountered 'CustomError()' whilst initializing; sleeping {desc}..."
659
662
  assert message == expected
660
663
 
661
- @given(logger=just("logger") | none())
664
+ @given(log=booleans())
662
665
  @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
663
666
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
664
667
  async def test_error_upon_core(
@@ -666,7 +669,7 @@ class TestInfiniteLooper:
666
669
  *,
667
670
  sleep_restart: DurationOrEveryDuration,
668
671
  desc: str,
669
- logger: str | None,
672
+ log: bool,
670
673
  caplog: LogCaptureFixture,
671
674
  ) -> None:
672
675
  class CustomError(Exception): ...
@@ -679,15 +682,19 @@ class TestInfiniteLooper:
679
682
 
680
683
  async with (
681
684
  timeout(1.0),
682
- Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger),
685
+ Example(
686
+ sleep_core=0.1,
687
+ sleep_restart=sleep_restart,
688
+ logger=unique_str() if log else None,
689
+ ),
683
690
  ):
684
691
  ...
685
- if logger is not None:
692
+ if log:
686
693
  message = caplog.messages[0]
687
694
  expected = f"'Example' encountered 'CustomError()'; sleeping {desc}..."
688
695
  assert message == expected
689
696
 
690
- @given(logger=just("logger") | none())
697
+ @given(log=booleans())
691
698
  @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
692
699
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
693
700
  async def test_error_upon_teardown(
@@ -695,7 +702,7 @@ class TestInfiniteLooper:
695
702
  *,
696
703
  sleep_restart: DurationOrEveryDuration,
697
704
  desc: str,
698
- logger: str | None,
705
+ log: bool,
699
706
  caplog: LogCaptureFixture,
700
707
  ) -> None:
701
708
  class Custom1Error(Exception): ...
@@ -724,14 +731,18 @@ class TestInfiniteLooper:
724
731
 
725
732
  async with (
726
733
  timeout(1.0),
727
- Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger),
734
+ Example(
735
+ sleep_core=0.1,
736
+ sleep_restart=sleep_restart,
737
+ logger=unique_str() if log else None,
738
+ ),
728
739
  ):
729
740
  ...
730
- if logger is not None:
741
+ if log:
731
742
  expected = f"'Example' encountered 'Custom2Error()' whilst tearing down; sleeping {desc}..."
732
743
  assert expected in caplog.messages
733
744
 
734
- @given(logger=just("logger") | none())
745
+ @given(log=booleans())
735
746
  @mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
736
747
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
737
748
  async def test_error_group_upon_others(
@@ -739,7 +750,7 @@ class TestInfiniteLooper:
739
750
  *,
740
751
  sleep_restart: DurationOrEveryDuration,
741
752
  desc: str,
742
- logger: str | None,
753
+ log: bool,
743
754
  caplog: LogCaptureFixture,
744
755
  ) -> None:
745
756
  class CustomError(Exception): ...
@@ -770,10 +781,14 @@ class TestInfiniteLooper:
770
781
 
771
782
  async with (
772
783
  timeout(1.0),
773
- Example(sleep_core=0.05, sleep_restart=sleep_restart, logger=logger),
784
+ Example(
785
+ sleep_core=0.05,
786
+ sleep_restart=sleep_restart,
787
+ logger=unique_str() if log else None,
788
+ ),
774
789
  ):
775
790
  ...
776
- if logger is not None:
791
+ if log:
777
792
  message = caplog.messages[0]
778
793
  expected = f"""\
779
794
  'Example' encountered 1 error(s):
@@ -867,11 +882,10 @@ class TestInfiniteQueueLooper:
867
882
  await looper.run_until_empty(stop=True)
868
883
  assert looper.empty()
869
884
 
870
- @given(logger=just("logger") | none())
871
- @mark.flaky
885
+ @given(log=booleans())
872
886
  @settings(suppress_health_check={HealthCheck.function_scoped_fixture})
873
887
  async def test_error_process_items(
874
- self, *, logger: str | None, caplog: LogCaptureFixture
888
+ self, *, log: bool, caplog: LogCaptureFixture
875
889
  ) -> None:
876
890
  class CustomError(Exception): ...
877
891
 
@@ -883,9 +897,12 @@ class TestInfiniteQueueLooper:
883
897
  async def _process_queue(self) -> None:
884
898
  raise CustomError
885
899
 
886
- async with timeout(1.0), Example(sleep_core=0.05, logger=logger) as looper:
900
+ async with (
901
+ timeout(1.0),
902
+ Example(sleep_core=0.05, logger=unique_str() if log else None) as looper,
903
+ ):
887
904
  looper.put_left_nowait(1)
888
- if logger is not None:
905
+ if log:
889
906
  message = caplog.messages[0]
890
907
  expected = "'Example' encountered 'CustomError()'; sleeping for 0:01:00..."
891
908
  assert message == expected
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Literal, override
5
5
 
6
6
  from pytest import approx
7
7
 
8
+ from tests.conftest import IS_CI
8
9
  from utilities.asyncio import Looper
9
10
  from utilities.contextlib import suppress_super_object_attribute_error
10
11
  from utilities.datetime import MILLISECOND
@@ -16,7 +17,7 @@ if TYPE_CHECKING:
16
17
 
17
18
  _FREQ: Duration = 10 * MILLISECOND
18
19
  _BACKOFF: Duration = 100 * MILLISECOND
19
- _REL: float = 0.75
20
+ _REL: float = 2.0 if IS_CI else 0.25
20
21
 
21
22
 
22
23
  # assert
@@ -4,7 +4,6 @@ import datetime as dt
4
4
  from itertools import pairwise
5
5
  from pathlib import Path
6
6
  from re import search
7
- from subprocess import PIPE, check_output
8
7
  from typing import TYPE_CHECKING, Any, cast
9
8
 
10
9
  from hypothesis import HealthCheck, Phase, assume, given, settings
@@ -45,7 +44,6 @@ from utilities.datetime import (
45
44
  parse_two_digit_year,
46
45
  )
47
46
  from utilities.functions import ensure_int
48
- from utilities.git import _GIT_REMOTE_GET_URL_ORIGIN, _GIT_REV_PARSE_ABBREV_REV_HEAD
49
47
  from utilities.hypothesis import (
50
48
  _SQLALCHEMY_ENGINE_DIALECTS,
51
49
  MaybeSearchStrategy,
@@ -590,21 +588,9 @@ class TestGitRepos:
590
588
  @given(data=data())
591
589
  @settings_with_reduced_examples()
592
590
  def test_main(self, *, data: DataObject) -> None:
593
- branch = data.draw(text_ascii(min_size=1) | none())
594
- remote = data.draw(text_ascii(min_size=1) | none())
595
- root = data.draw(git_repos(branch=branch, remote=remote))
591
+ root = data.draw(git_repos())
596
592
  files = set(root.iterdir())
597
593
  assert Path(root, ".git") in files
598
- if branch is not None:
599
- output = check_output(
600
- _GIT_REV_PARSE_ABBREV_REV_HEAD, stderr=PIPE, cwd=root, text=True
601
- )
602
- assert output.strip("\n") == branch
603
- if remote is not None:
604
- output = check_output(
605
- _GIT_REMOTE_GET_URL_ORIGIN, stderr=PIPE, cwd=root, text=True
606
- )
607
- assert output.strip("\n") == remote
608
594
 
609
595
 
610
596
  class TestHashables:
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Self
6
+
7
+ from hypothesis import given, settings
8
+ from hypothesis.strategies import integers, sets
9
+ from pytest import mark, param, raises
10
+
11
+ from utilities.dataclasses import replace_non_sentinel
12
+ from utilities.hypothesis import git_repos, paths, temp_paths
13
+ from utilities.pathlib import (
14
+ GetRootError,
15
+ ensure_suffix,
16
+ get_path,
17
+ get_root,
18
+ list_dir,
19
+ temp_cwd,
20
+ )
21
+ from utilities.sentinel import Sentinel, sentinel
22
+
23
+ if TYPE_CHECKING:
24
+ from utilities.types import MaybeCallablePathLike
25
+
26
+
27
+ class TestEnsureSuffix:
28
+ @mark.parametrize(
29
+ ("path", "suffix", "expected"),
30
+ [
31
+ param("foo", ".txt", "foo.txt"),
32
+ param("foo.txt", ".txt", "foo.txt"),
33
+ param("foo.bar.baz", ".baz", "foo.bar.baz"),
34
+ param("foo.bar.baz", ".quux", "foo.bar.baz.quux"),
35
+ ],
36
+ ids=str,
37
+ )
38
+ def test_main(self, *, path: Path, suffix: str, expected: str) -> None:
39
+ result = str(ensure_suffix(path, suffix))
40
+ assert result == expected
41
+
42
+
43
+ class TestGetPath:
44
+ @given(path=paths())
45
+ def test_path(self, *, path: Path) -> None:
46
+ assert get_path(path=path) == path
47
+
48
+ @given(path=paths())
49
+ def test_str(self, *, path: Path) -> None:
50
+ assert get_path(path=str(path)) == path
51
+
52
+ def test_none(self) -> None:
53
+ assert get_path(path=None) == Path.cwd()
54
+
55
+ def test_sentinel(self) -> None:
56
+ assert get_path(path=sentinel) is sentinel
57
+
58
+ @given(path1=paths(), path2=paths())
59
+ def test_replace_non_sentinel(self, *, path1: Path, path2: Path) -> None:
60
+ @dataclass(kw_only=True, slots=True)
61
+ class Example:
62
+ path: Path = field(default_factory=Path.cwd)
63
+
64
+ def replace(
65
+ self, *, path: MaybeCallablePathLike | Sentinel = sentinel
66
+ ) -> Self:
67
+ return replace_non_sentinel(self, path=get_path(path=path))
68
+
69
+ obj = Example(path=path1)
70
+ assert obj.path == path1
71
+ assert obj.replace().path == path1
72
+ assert obj.replace(path=path2).path == path2
73
+
74
+ @given(path=paths())
75
+ def test_callable(self, *, path: Path) -> None:
76
+ assert get_path(path=lambda: path) == path
77
+
78
+
79
+ class TestGetRoot:
80
+ @given(repo=git_repos())
81
+ @settings(max_examples=1)
82
+ def test_git(self, *, repo: Path) -> None:
83
+ root = get_root(path=repo)
84
+ expected = repo.resolve()
85
+ assert root == expected
86
+
87
+ @given(root=temp_paths())
88
+ @settings(max_examples=1)
89
+ def test_envrc(self, *, root: Path) -> None:
90
+ root.joinpath(".envrc").touch()
91
+ result = get_root(path=root)
92
+ assert result == root
93
+
94
+ @given(root=temp_paths())
95
+ @settings(max_examples=1)
96
+ def test_envrc_from_inside(self, *, root: Path) -> None:
97
+ root.joinpath(".envrc").touch()
98
+ path = root.joinpath("foo", "bar", "baz")
99
+ path.mkdir(parents=True)
100
+ result = get_root(path=path)
101
+ assert result == root
102
+
103
+ def test_error(self, *, tmp_path: Path) -> None:
104
+ with raises(GetRootError, match="Unable to determine root from '.*'"):
105
+ _ = get_root(path=tmp_path)
106
+
107
+
108
+ class TestListDir:
109
+ @given(root=temp_paths(), nums=sets(integers(0, 100), max_size=10))
110
+ def test_main(self, *, root: Path, nums: set[str]) -> None:
111
+ for n in nums:
112
+ path = root.joinpath(f"{n}.txt")
113
+ path.touch()
114
+ result = list_dir(root)
115
+ expected = sorted(Path(root, f"{n}.txt") for n in nums)
116
+ assert result == expected
117
+
118
+
119
+ class TestTempCWD:
120
+ def test_main(self, *, tmp_path: Path) -> None:
121
+ assert Path.cwd() != tmp_path
122
+ with temp_cwd(tmp_path):
123
+ assert Path.cwd() == tmp_path
124
+ assert Path.cwd() != tmp_path
@@ -57,10 +57,10 @@ class TestLoadSettings:
57
57
  key_env = data.draw(sampled_from(["key", "KEY"]))
58
58
  value_env = data.draw(text_ascii())
59
59
  with temp_environ({key_env: value_env}):
60
- settings = load_settings(SettingsUse, cwd=root)
60
+ settings = load_settings(SettingsUse, path=root)
61
61
  exp_value = value_env
62
62
  else:
63
- settings = load_settings(SettingsUse, cwd=root)
63
+ settings = load_settings(SettingsUse, path=root)
64
64
  exp_value = value_file
65
65
 
66
66
  if SettingsUse is SettingsLower:
@@ -82,7 +82,7 @@ class TestLoadSettings:
82
82
  _ = fh.write(f"key = {value}\n")
83
83
  _ = fh.write(f"other = {value}\n")
84
84
 
85
- settings = load_settings(Settings, cwd=root)
85
+ settings = load_settings(Settings, path=root)
86
86
  expected = Settings(key=value)
87
87
  assert settings == expected
88
88
 
@@ -94,7 +94,7 @@ class TestLoadSettings:
94
94
  KEY: str
95
95
 
96
96
  with raises(_LoadSettingsFileNotFoundError, match=r"Path '.*' must exist"):
97
- _ = load_settings(Settings, cwd=root)
97
+ _ = load_settings(Settings, path=root)
98
98
 
99
99
  @given(root=git_repos(), value=integers())
100
100
  @settings_with_reduced_examples()
@@ -114,7 +114,7 @@ class TestLoadSettings:
114
114
  flags=DOTALL,
115
115
  ),
116
116
  ):
117
- _ = load_settings(Settings, cwd=root)
117
+ _ = load_settings(Settings, path=root)
118
118
 
119
119
  @given(root=git_repos())
120
120
  @settings_with_reduced_examples()
@@ -129,4 +129,4 @@ class TestLoadSettings:
129
129
  _LoadSettingsMissingKeysError,
130
130
  match=r"Unable to load '.*'; missing value\(s\) for 'key'",
131
131
  ):
132
- _ = load_settings(Settings, cwd=root)
132
+ _ = load_settings(Settings, path=root)
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.127.0"
3
+ __version__ = "0.128.0"
@@ -509,14 +509,6 @@ def get_datetime(*, datetime: MaybeCallableDateTime) -> dt.datetime: ...
509
509
  def get_datetime(*, datetime: None) -> None: ...
510
510
  @overload
511
511
  def get_datetime(*, datetime: Sentinel) -> Sentinel: ...
512
- @overload
513
- def get_datetime(
514
- *, datetime: MaybeCallableDateTime | Sentinel
515
- ) -> dt.datetime | Sentinel: ...
516
- @overload
517
- def get_datetime(
518
- *, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
519
- ) -> dt.datetime | None | Sentinel: ...
520
512
  def get_datetime(
521
513
  *, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
522
514
  ) -> dt.datetime | None | Sentinel:
@@ -506,13 +506,7 @@ def floats_extra(
506
506
 
507
507
 
508
508
  @composite
509
- def git_repos(
510
- draw: DrawFn,
511
- /,
512
- *,
513
- branch: MaybeSearchStrategy[str | None] = None,
514
- remote: MaybeSearchStrategy[str | None] = None,
515
- ) -> Path:
509
+ def git_repos(draw: DrawFn, /) -> Path:
516
510
  path = draw(temp_paths())
517
511
  with temp_cwd(path):
518
512
  _ = check_call(["git", "init", "-b", "master"])
@@ -525,10 +519,6 @@ def git_repos(
525
519
  _ = check_call(["git", "commit", "-m", "add"])
526
520
  _ = check_call(["git", "rm", file_str])
527
521
  _ = check_call(["git", "commit", "-m", "rm"])
528
- if (branch_ := draw2(draw, branch)) is not None:
529
- _ = check_call(["git", "checkout", "-b", branch_])
530
- if (remote_ := draw2(draw, remote)) is not None:
531
- _ = check_call(["git", "remote", "add", "origin", remote_])
532
522
  return path
533
523
 
534
524
 
@@ -46,9 +46,8 @@ from utilities.datetime import (
46
46
  serialize_compact,
47
47
  )
48
48
  from utilities.errors import ImpossibleCaseError
49
- from utilities.git import get_repo_root
50
49
  from utilities.iterables import OneEmptyError, always_iterable, one
51
- from utilities.pathlib import ensure_suffix, resolve_path
50
+ from utilities.pathlib import ensure_suffix, get_path, get_root
52
51
  from utilities.reprlib import (
53
52
  RICH_EXPAND_ALL,
54
53
  RICH_INDENT_SIZE,
@@ -68,9 +67,9 @@ if TYPE_CHECKING:
68
67
  from utilities.types import (
69
68
  LoggerOrName,
70
69
  LogLevel,
70
+ MaybeCallablePathLike,
71
71
  MaybeIterable,
72
72
  PathLike,
73
- PathLikeOrCallable,
74
73
  )
75
74
  from utilities.version import MaybeCallableVersionLike
76
75
 
@@ -383,10 +382,10 @@ class StandaloneFileHandler(Handler):
383
382
 
384
383
  @override
385
384
  def __init__(
386
- self, *, level: int = NOTSET, path: PathLikeOrCallable | None = None
385
+ self, *, level: int = NOTSET, path: MaybeCallablePathLike | None = None
387
386
  ) -> None:
388
387
  super().__init__(level=level)
389
- self._path = path
388
+ self._path = get_path(path=path)
390
389
 
391
390
  @override
392
391
  def emit(self, record: LogRecord) -> None:
@@ -394,10 +393,8 @@ class StandaloneFileHandler(Handler):
394
393
  from utilities.tzlocal import get_now_local
395
394
 
396
395
  try:
397
- path = (
398
- resolve_path(path=self._path)
399
- .joinpath(serialize_compact(get_now_local()))
400
- .with_suffix(".txt")
396
+ path = self._path.joinpath(serialize_compact(get_now_local())).with_suffix(
397
+ ".txt"
401
398
  )
402
399
  formatted = self.format(record)
403
400
  with writer(path, overwrite=True) as temp, temp.open(mode="w") as fh:
@@ -473,7 +470,7 @@ class FilterForKeyError(Exception):
473
470
 
474
471
  def get_default_logging_path() -> Path:
475
472
  """Get the logging default path."""
476
- return get_repo_root().joinpath(".logs")
473
+ return get_root().joinpath(".logs")
477
474
 
478
475
 
479
476
  ##
@@ -520,7 +517,7 @@ def setup_logging(
520
517
  console_level: LogLevel | None = "INFO",
521
518
  console_filters: Iterable[_FilterType] | None = None,
522
519
  console_fmt: str = "❯ {_zoned_datetime_str} | {name}:{funcName}:{lineno} | {message}", # noqa: RUF001
523
- files_dir: PathLikeOrCallable | None = get_default_logging_path,
520
+ files_dir: MaybeCallablePathLike | None = get_default_logging_path,
524
521
  files_when: _When = "D",
525
522
  files_interval: int = 1,
526
523
  files_backup_count: int = 10,
@@ -616,7 +613,7 @@ def setup_logging(
616
613
  logger_use.addHandler(console_high_and_exc_handler)
617
614
 
618
615
  # debug & info
619
- directory = resolve_path(path=files_dir) # skipif-ci-and-windows
616
+ directory = get_path(path=files_dir) # skipif-ci-and-windows
620
617
  levels: list[LogLevel] = ["DEBUG", "INFO"] # skipif-ci-and-windows
621
618
  for level, (subpath, files_or_plain_formatter) in product( # skipif-ci-and-windows
622
619
  levels, [(Path(), files_formatter), (Path("plain"), plain_formatter)]
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from contextlib import contextmanager, suppress
5
+ from dataclasses import dataclass
6
+ from itertools import chain
7
+ from os import chdir
8
+ from pathlib import Path
9
+ from re import IGNORECASE, search
10
+ from subprocess import PIPE, CalledProcessError, check_output
11
+ from typing import TYPE_CHECKING, assert_never, overload, override
12
+
13
+ from utilities.sentinel import Sentinel, sentinel
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Iterator, Sequence
17
+
18
+ from utilities.types import MaybeCallablePathLike, PathLike
19
+
20
+ PWD = Path.cwd()
21
+
22
+
23
+ def ensure_suffix(path: PathLike, suffix: str, /) -> Path:
24
+ """Ensure a path has a given suffix."""
25
+ path = Path(path)
26
+ parts = path.name.split(".")
27
+ parts = list(chain([parts[0]], (f".{p}" for p in parts[1:])))
28
+ if (len(parts) == 0) or (parts[-1] != suffix):
29
+ parts.append(suffix)
30
+ name = "".join(parts)
31
+ return path.with_name(name)
32
+
33
+
34
+ ##
35
+
36
+
37
+ @overload
38
+ def get_path(*, path: MaybeCallablePathLike | None) -> Path: ...
39
+ @overload
40
+ def get_path(*, path: Sentinel) -> Sentinel: ...
41
+ def get_path(
42
+ *, path: MaybeCallablePathLike | None | Sentinel = sentinel
43
+ ) -> Path | None | Sentinel:
44
+ """Get the path."""
45
+ match path:
46
+ case Path() | Sentinel():
47
+ return path
48
+ case str():
49
+ return Path(path)
50
+ case None:
51
+ return Path.cwd()
52
+ case Callable() as func:
53
+ return get_path(path=func())
54
+ case _ as never:
55
+ assert_never(never)
56
+
57
+
58
+ ##
59
+
60
+
61
+ def get_root(*, path: MaybeCallablePathLike | None = None) -> Path:
62
+ """Get the root of a path."""
63
+ path = get_path(path=path)
64
+ try:
65
+ output = check_output(
66
+ ["git", "rev-parse", "--show-toplevel"], stderr=PIPE, cwd=path, text=True
67
+ )
68
+ except CalledProcessError as error:
69
+ # newer versions of git report "Not a git repository", whilst older
70
+ # versions report "not a git repository"
71
+ if not search("fatal: not a git repository", error.stderr, flags=IGNORECASE):
72
+ raise # pragma: no cover
73
+ else:
74
+ return Path(output.strip("\n"))
75
+ all_paths = list(chain([path], path.parents))
76
+ with suppress(StopIteration):
77
+ return next(
78
+ p for p in all_paths if any(p_i.name == ".envrc" for p_i in p.iterdir())
79
+ )
80
+ raise GetRootError(path=path)
81
+
82
+
83
+ @dataclass(kw_only=True, slots=True)
84
+ class GetRootError(Exception):
85
+ path: PathLike
86
+
87
+ @override
88
+ def __str__(self) -> str:
89
+ return f"Unable to determine root from {str(self.path)!r}"
90
+
91
+
92
+ ##
93
+
94
+
95
+ def list_dir(path: PathLike, /) -> Sequence[Path]:
96
+ """List the contents of a directory."""
97
+ return sorted(Path(path).iterdir())
98
+
99
+
100
+ ##
101
+
102
+
103
+ @contextmanager
104
+ def temp_cwd(path: PathLike, /) -> Iterator[None]:
105
+ """Context manager with temporary current working directory set."""
106
+ prev = Path.cwd()
107
+ chdir(path)
108
+ try:
109
+ yield
110
+ finally:
111
+ chdir(prev)
112
+
113
+
114
+ __all__ = ["PWD", "ensure_suffix", "get_path", "list_dir", "temp_cwd"]