dycw-utilities 0.127.1__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.1 → dycw_utilities-0.128.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/pyproject.toml +3 -2
  3. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/loopers.py +2 -1
  4. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_hypothesis.py +1 -15
  5. dycw_utilities-0.128.0/src/tests/test_pathlib.py +124 -0
  6. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_python_dotenv.py +6 -6
  7. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/__init__.py +1 -1
  8. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/datetime.py +0 -8
  9. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/hypothesis.py +1 -11
  10. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/logging.py +9 -12
  11. dycw_utilities-0.128.0/src/utilities/pathlib.py +114 -0
  12. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pyinstrument.py +6 -4
  13. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pytest_regressions.py +2 -2
  14. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/python_dotenv.py +10 -6
  15. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/types.py +2 -2
  16. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/version.py +0 -8
  17. dycw_utilities-0.127.1/src/tests/test_git.py +0 -71
  18. dycw_utilities-0.127.1/src/tests/test_pathlib.py +0 -60
  19. dycw_utilities-0.127.1/src/utilities/git.py +0 -93
  20. dycw_utilities-0.127.1/src/utilities/pathlib.py +0 -55
  21. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/.gitignore +0 -0
  22. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/LICENSE +0 -0
  23. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/README.md +0 -0
  24. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/__init__.py +0 -0
  25. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/conftest.py +0 -0
  26. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/__init__.py +0 -0
  27. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_missing/__init__.py +0 -0
  28. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_missing/module.py +0 -0
  29. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/__init__.py +0 -0
  30. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/outer_1.py +0 -0
  31. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/outer_2.py +0 -0
  32. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  33. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  34. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  35. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  36. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_without/__init__.py +0 -0
  37. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_without/module_1.py +0 -0
  38. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/package_without/module_2.py +0 -0
  39. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/standalone.py +0 -0
  40. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/modules/with_imports.py +0 -0
  41. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  42. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  43. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  44. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  45. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  46. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  47. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  48. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  49. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_altair.py +0 -0
  50. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_asyncio.py +0 -0
  51. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/__init__.py +0 -0
  52. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/redis.py +0 -0
  53. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_atomicwrites.py +0 -0
  54. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_atools.py +0 -0
  55. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_cachetools.py +0 -0
  56. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_click.py +0 -0
  57. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_concurrent.py +0 -0
  58. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_contextlib.py +0 -0
  59. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_contextvars.py +0 -0
  60. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_cryptography.py +0 -0
  61. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_cvxpy.py +0 -0
  62. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_dataclasses.py +0 -0
  63. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_datetime.py +0 -0
  64. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_enum.py +0 -0
  65. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_errors.py +0 -0
  66. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_eventkit.py +0 -0
  67. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_fastapi.py +0 -0
  68. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_fpdf2.py +0 -0
  69. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_functions.py +0 -0
  70. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_functools.py +0 -0
  71. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_getpass.py +0 -0
  72. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_hashlib.py +0 -0
  73. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_http.py +0 -0
  74. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_importlib.py +0 -0
  75. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_ipython.py +0 -0
  76. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_iterables.py +0 -0
  77. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_jupyter.py +0 -0
  78. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_libcst.py +0 -0
  79. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_lightweight_charts.py +0 -0
  80. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_logging.py +0 -0
  81. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_loguru.py +0 -0
  82. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_luigi.py +0 -0
  83. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_math.py +0 -0
  84. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_memory_profiler.py +0 -0
  85. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_modules.py +0 -0
  86. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_more_itertools.py +0 -0
  87. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_numpy.py +0 -0
  88. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_operator.py +0 -0
  89. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_optuna.py +0 -0
  90. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_orjson.py +0 -0
  91. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_os.py +0 -0
  92. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_parse.py +0 -0
  93. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_period.py +0 -0
  94. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pickle.py +0 -0
  95. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_platform.py +0 -0
  96. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_polars.py +0 -0
  97. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_polars_ols.py +0 -0
  98. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pottery.py +0 -0
  99. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pqdm.py +0 -0
  100. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_psutil.py +0 -0
  101. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pydantic.py +0 -0
  102. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pyinstrument.py +0 -0
  103. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pyrsistent.py +0 -0
  104. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pytest.py +0 -0
  105. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_pytest_regressions.py +0 -0
  106. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_random.py +0 -0
  107. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_re.py +0 -0
  108. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_redis.py +0 -0
  109. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_reprlib.py +0 -0
  110. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_scipy.py +0 -0
  111. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_sentinel.py +0 -0
  112. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_shelve.py +0 -0
  113. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_slack_sdk.py +0 -0
  114. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_socket.py +0 -0
  115. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_sqlalchemy.py +0 -0
  116. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  117. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_statsmodel.py +0 -0
  118. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_streamlit.py +0 -0
  119. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_string.py +0 -0
  120. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_sys.py +0 -0
  121. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_tempfile.py +0 -0
  122. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_tenacity.py +0 -0
  123. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_text.py +0 -0
  124. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_threading.py +0 -0
  125. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_timer.py +0 -0
  126. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback.py +0 -0
  127. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
  128. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/chain.py +0 -0
  129. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  130. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  131. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  132. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/many.py +0 -0
  133. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/one.py +0 -0
  134. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
  135. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  136. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  137. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/two.py +0 -0
  138. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
  139. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_types.py +0 -0
  140. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_typing.py +0 -0
  141. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  142. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  143. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  144. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_tzdata.py +0 -0
  145. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_tzlocal.py +0 -0
  146. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_uuid.py +0 -0
  147. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_version.py +0 -0
  148. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_warnings.py +0 -0
  149. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_whenever.py +0 -0
  150. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_zipfile.py +0 -0
  151. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/tests/test_zoneinfo.py +0 -0
  152. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/altair.py +0 -0
  153. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/asyncio.py +0 -0
  154. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/atomicwrites.py +0 -0
  155. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/atools.py +0 -0
  156. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/cachetools.py +0 -0
  157. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/click.py +0 -0
  158. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/concurrent.py +0 -0
  159. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/contextlib.py +0 -0
  160. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/contextvars.py +0 -0
  161. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/cryptography.py +0 -0
  162. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/cvxpy.py +0 -0
  163. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/dataclasses.py +0 -0
  164. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/enum.py +0 -0
  165. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/errors.py +0 -0
  166. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/eventkit.py +0 -0
  167. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/fastapi.py +0 -0
  168. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/fpdf2.py +0 -0
  169. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/functions.py +0 -0
  170. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/functools.py +0 -0
  171. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/getpass.py +0 -0
  172. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/hashlib.py +0 -0
  173. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/http.py +0 -0
  174. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/importlib.py +0 -0
  175. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/ipython.py +0 -0
  176. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/iterables.py +0 -0
  177. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/jupyter.py +0 -0
  178. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/libcst.py +0 -0
  179. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/lightweight_charts.py +0 -0
  180. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/loguru.py +0 -0
  181. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/luigi.py +0 -0
  182. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/math.py +0 -0
  183. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/memory_profiler.py +0 -0
  184. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/modules.py +0 -0
  185. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/more_itertools.py +0 -0
  186. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/numpy.py +0 -0
  187. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/operator.py +0 -0
  188. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/optuna.py +0 -0
  189. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/orjson.py +0 -0
  190. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/os.py +0 -0
  191. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/parse.py +0 -0
  192. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/period.py +0 -0
  193. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pickle.py +0 -0
  194. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/platform.py +0 -0
  195. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/polars.py +0 -0
  196. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/polars_ols.py +0 -0
  197. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pottery.py +0 -0
  198. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pqdm.py +0 -0
  199. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/psutil.py +0 -0
  200. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/py.typed +0 -0
  201. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pydantic.py +0 -0
  202. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pyrsistent.py +0 -0
  203. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/pytest.py +0 -0
  204. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/random.py +0 -0
  205. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/re.py +0 -0
  206. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/redis.py +0 -0
  207. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/reprlib.py +0 -0
  208. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/scipy.py +0 -0
  209. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/sentinel.py +0 -0
  210. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/shelve.py +0 -0
  211. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/slack_sdk.py +0 -0
  212. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/socket.py +0 -0
  213. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/sqlalchemy.py +0 -0
  214. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/sqlalchemy_polars.py +0 -0
  215. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/statsmodels.py +0 -0
  216. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/streamlit.py +0 -0
  217. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/string.py +0 -0
  218. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/sys.py +0 -0
  219. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/tempfile.py +0 -0
  220. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/tenacity.py +0 -0
  221. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/text.py +0 -0
  222. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/threading.py +0 -0
  223. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/timer.py +0 -0
  224. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/traceback.py +0 -0
  225. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/typing.py +0 -0
  226. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/tzdata.py +0 -0
  227. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/tzlocal.py +0 -0
  228. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/uuid.py +0 -0
  229. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/warnings.py +0 -0
  230. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/whenever.py +0 -0
  231. {dycw_utilities-0.127.1 → dycw_utilities-0.128.0}/src/utilities/zipfile.py +0 -0
  232. {dycw_utilities-0.127.1 → 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.1
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.1"
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.1"
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
@@ -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.1"
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"]
@@ -7,23 +7,25 @@ from typing import TYPE_CHECKING
7
7
  from pyinstrument.profiler import Profiler
8
8
 
9
9
  from utilities.datetime import serialize_compact
10
- from utilities.pathlib import PWD
10
+ from utilities.pathlib import get_path
11
11
  from utilities.tzlocal import get_now_local
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Iterator
15
15
 
16
- from utilities.types import PathLike
16
+ from utilities.types import MaybeCallablePathLike
17
17
 
18
18
 
19
19
  @contextmanager
20
- def profile(*, path: PathLike = PWD) -> Iterator[None]:
20
+ def profile(*, path: MaybeCallablePathLike | None = Path.cwd) -> Iterator[None]:
21
21
  """Profile the contents of a block."""
22
22
  from utilities.atomicwrites import writer
23
23
 
24
24
  with Profiler() as profiler:
25
25
  yield
26
- filename = Path(path, f"profile__{serialize_compact(get_now_local())}.html")
26
+ filename = get_path(path=path).joinpath(
27
+ f"profile__{serialize_compact(get_now_local())}.html"
28
+ )
27
29
  with writer(filename) as temp, temp.open(mode="w") as fh:
28
30
  _ = fh.write(profiler.output_html())
29
31
 
@@ -10,8 +10,8 @@ from pytest import fixture
10
10
  from pytest_regressions.file_regression import FileRegressionFixture
11
11
 
12
12
  from utilities.functions import ensure_str
13
- from utilities.git import get_repo_root
14
13
  from utilities.operator import is_equal
14
+ from utilities.pathlib import get_root
15
15
  from utilities.pytest import node_id_to_path
16
16
 
17
17
  if TYPE_CHECKING:
@@ -153,7 +153,7 @@ def polars_regression(
153
153
 
154
154
  def _get_path(request: FixtureRequest, /) -> Path:
155
155
  tail = node_id_to_path(request.node.nodeid, head=_PATH_TESTS)
156
- return get_repo_root().joinpath(_PATH_TESTS, "regressions", tail)
156
+ return get_root().joinpath(_PATH_TESTS, "regressions", tail)
157
157
 
158
158
 
159
159
  __all__ = [
@@ -2,29 +2,33 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from os import environ
5
+ from pathlib import Path
5
6
  from typing import TYPE_CHECKING, override
6
7
 
7
8
  from dotenv import dotenv_values
8
9
 
9
10
  from utilities.dataclasses import _ParseDataClassMissingValuesError, parse_dataclass
10
- from utilities.git import get_repo_root
11
11
  from utilities.iterables import MergeStrMappingsError, merge_str_mappings
12
- from utilities.pathlib import PWD
12
+ from utilities.pathlib import get_root
13
13
  from utilities.reprlib import get_repr
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from collections.abc import Mapping
17
17
  from collections.abc import Set as AbstractSet
18
- from pathlib import Path
19
18
 
20
- from utilities.types import ParseObjectExtra, PathLike, StrMapping, TDataclass
19
+ from utilities.types import (
20
+ MaybeCallablePathLike,
21
+ ParseObjectExtra,
22
+ StrMapping,
23
+ TDataclass,
24
+ )
21
25
 
22
26
 
23
27
  def load_settings(
24
28
  cls: type[TDataclass],
25
29
  /,
26
30
  *,
27
- cwd: PathLike = PWD,
31
+ path: MaybeCallablePathLike | None = Path.cwd,
28
32
  globalns: StrMapping | None = None,
29
33
  localns: StrMapping | None = None,
30
34
  warn_name_errors: bool = False,
@@ -33,7 +37,7 @@ def load_settings(
33
37
  extra_parsers: ParseObjectExtra | None = None,
34
38
  ) -> TDataclass:
35
39
  """Load a set of settings from the `.env` file."""
36
- path = get_repo_root(cwd=cwd).joinpath(".env")
40
+ path = get_root(path=path).joinpath(".env")
37
41
  if not path.exists():
38
42
  raise _LoadSettingsFileNotFoundError(path=path) from None
39
43
  maybe_values_dotenv = dotenv_values(path)
@@ -241,8 +241,8 @@ type SerializeObjectExtra = Mapping[Any, Callable[[Any], str]]
241
241
 
242
242
 
243
243
  # pathlib
244
+ type MaybeCallablePathLike = MaybeCallable[PathLike]
244
245
  type PathLike = MaybeStr[Path]
245
- type PathLikeOrCallable = PathLike | Callable[[], PathLike]
246
246
 
247
247
 
248
248
  # random
@@ -282,6 +282,7 @@ __all__ = [
282
282
  "MaybeCallableDate",
283
283
  "MaybeCallableDateTime",
284
284
  "MaybeCallableEvent",
285
+ "MaybeCallablePathLike",
285
286
  "MaybeCoroutine1",
286
287
  "MaybeIterable",
287
288
  "MaybeIterableHashable",
@@ -293,7 +294,6 @@ __all__ = [
293
294
  "Parallelism",
294
295
  "ParseObjectExtra",
295
296
  "PathLike",
296
- "PathLikeOrCallable",
297
297
  "RoundMode",
298
298
  "Seed",
299
299
  "SerializeObjectExtra",
@@ -137,14 +137,6 @@ def get_version(*, version: MaybeCallableVersionLike) -> Version: ...
137
137
  def get_version(*, version: None) -> None: ...
138
138
  @overload
139
139
  def get_version(*, version: Sentinel) -> Sentinel: ...
140
- @overload
141
- def get_version(
142
- *, version: MaybeCallableVersionLike | Sentinel
143
- ) -> Version | Sentinel: ...
144
- @overload
145
- def get_version(
146
- *, version: MaybeCallableVersionLike | None | Sentinel = sentinel
147
- ) -> Version | None | Sentinel: ...
148
140
  def get_version(
149
141
  *, version: MaybeCallableVersionLike | None | Sentinel = sentinel
150
142
  ) -> Version | None | Sentinel: