dycw-utilities 0.127.0__py3-none-any.whl → 0.128.0__py3-none-any.whl

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.
@@ -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
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=9_7tk0nJkuN-6qR4z_kQd3QZb3ioRi15dhZGN0m-284,60
1
+ utilities/__init__.py,sha256=eWwSSpaFIxxRG_Er2j_6n618a-HRkBuT-C-hli9i1bM,60
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/asyncio.py,sha256=wKxwNnxdWxsiy5U0b1F3UgpWRHlPKM0y_OcmURzqxR8,51396
4
4
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
@@ -11,7 +11,7 @@ utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
11
11
  utilities/cryptography.py,sha256=_CiK_K6c_-uQuUhsUNjNjTL-nqxAh4_1zTfS11Xe120,972
12
12
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
13
13
  utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
14
- utilities/datetime.py,sha256=uYoaOi_C1YtNXGfTN9xlTrW62Re2b1_4Skuv14_MeYQ,38985
14
+ utilities/datetime.py,sha256=aiPh2OZK2g9gn4yEeSO0lODOmvx8U_rGn6XeSzyk4VY,38738
15
15
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
16
16
  utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
17
17
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
@@ -20,17 +20,16 @@ utilities/fpdf2.py,sha256=y1NGXR5chWqLXWpewGV3hlRGMr_5yV1lVRkPBhPEgJI,1843
20
20
  utilities/functions.py,sha256=jgt592voaHNtX56qX0SRvFveVCRmSIxCZmqvpLZCnY8,27305
21
21
  utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
22
22
  utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
23
- utilities/git.py,sha256=wpt5dZ5Oi5931pN24_VLZYaQOvmR0OcQuVtgHzFUN1k,2359
24
23
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
25
24
  utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
26
- utilities/hypothesis.py,sha256=snJ35u9-dXKn3Unac4IPW2V4JtRUg5B7SsDBrQHIx9g,44834
25
+ utilities/hypothesis.py,sha256=UnUMJmeqwJuK7uyUqw_i3opUYzVKud4RMG0RMOSRBQY,44463
27
26
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
28
27
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
29
28
  utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
30
29
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
31
30
  utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
32
31
  utilities/lightweight_charts.py,sha256=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
33
- utilities/logging.py,sha256=gwo3pusPjnWO1ollrtn1VKYyRAQJTue4SkCbMeNvec4,25715
32
+ utilities/logging.py,sha256=a99gX9oQUe_Oxs5rDtTwUVuOwhRyeO_GfoFNKVaEny0,25641
34
33
  utilities/loguru.py,sha256=MEMQVWrdECxk1e3FxGzmOf21vWT9j8CAir98SEXFKPA,3809
35
34
  utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
36
35
  utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
@@ -43,7 +42,7 @@ utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
43
42
  utilities/orjson.py,sha256=AvPFxzJdxC-3PBID3cqdiMyN8FeC7aW9QUgGwbvKuAM,36948
44
43
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
45
44
  utilities/parse.py,sha256=vsZ2jf_ceSI_Kta9titixufysJaVXh0Whjz1T4awJZw,18938
46
- utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
45
+ utilities/pathlib.py,sha256=0cQpqmZs-Pe2693xZwGFApq-B9mADqhX-pclf_5iLco,3041
47
46
  utilities/period.py,sha256=o4wXYEXVlFomop4-Ra4L0yRP4i99NZFjIe_fa7NdZck,11024
48
47
  utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
49
48
  utilities/platform.py,sha256=48IOKx1IC6ZJXWG-b56ZQptITcNFhWRjELW72o2dGTA,2398
@@ -54,11 +53,11 @@ utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
54
53
  utilities/psutil.py,sha256=RtbLKOoIJhqrJmEoHDBVeSD-KPzshtS0FtRXBP9_w2s,3751
55
54
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
55
  utilities/pydantic.py,sha256=f6qtR5mO2YMuyvNmbaEj5YeD9eGA4YYfb7Bjzh9jUs0,1845
57
- utilities/pyinstrument.py,sha256=OJFDh4o1CWIa4aYPYURdQjgap_nvP45KUsCEe94rQHY,829
56
+ utilities/pyinstrument.py,sha256=O2dngLsmUUnpMtW1eN3OiM0rGQNBIlXSvZmym_jAsvU,904
58
57
  utilities/pyrsistent.py,sha256=wVOVIe_68AAaa-lUE9y-TEzDawVp1uEIc_zfoDgr5ww,2287
59
58
  utilities/pytest.py,sha256=KoHSwJbIY2CHtFUlUr_gnEk7z1DVTaldl8RDQ4tDkG4,7837
60
- utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq3c,5088
61
- utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
59
+ utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
60
+ utilities/python_dotenv.py,sha256=edXsvHZhZnYeqfMfrsRRpj7_9eJI6uizh3xLx8Q9B3w,3228
62
61
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
63
62
  utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
64
63
  utilities/redis.py,sha256=EZgqWeoGpvN-BfCQL93F3rYlfB4U_zhzHCBuZpDmKpo,37157
@@ -80,17 +79,17 @@ utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
80
79
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
81
80
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
82
81
  utilities/traceback.py,sha256=Jg7HS3AwQ-W-msdwHp22_PSHZcR54PbmsSf115B6TSM,27435
83
- utilities/types.py,sha256=2f1DqTZTMRlpCPWPd9-rh_uwmRPv9UdBoi_Bfv7Ccmo,18374
82
+ utilities/types.py,sha256=gP04CcCOyFrG7BgblVCsrrChiuO2x842NDVW-GF7odo,18370
84
83
  utilities/typing.py,sha256=H6ysJkI830aRwLsMKz0SZIw4cpcsm7d6KhQOwr-SDh0,13817
85
84
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
86
85
  utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
87
86
  utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
88
- utilities/version.py,sha256=QFuyEeQA6jI0ruBEcmhqG36f-etg1AEiD1drBBqhQrs,5358
87
+ utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
89
88
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
90
89
  utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
91
90
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
92
91
  utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
93
- dycw_utilities-0.127.0.dist-info/METADATA,sha256=bbY-L7Ck2mkOycI3XCTxhqptadPeM716AJz69ryeIXw,12803
94
- dycw_utilities-0.127.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
- dycw_utilities-0.127.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
96
- dycw_utilities-0.127.0.dist-info/RECORD,,
92
+ dycw_utilities-0.128.0.dist-info/METADATA,sha256=BUxspfSeFNdeLBrh89gZYdXyqBuAr4yzLZ_ylcVOBlI,12803
93
+ dycw_utilities-0.128.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
+ dycw_utilities-0.128.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.128.0.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.127.0"
3
+ __version__ = "0.128.0"
utilities/datetime.py CHANGED
@@ -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:
utilities/hypothesis.py CHANGED
@@ -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
 
utilities/logging.py CHANGED
@@ -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)]
utilities/pathlib.py CHANGED
@@ -1,15 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
- from contextlib import contextmanager
3
+ from collections.abc import Callable
4
+ from contextlib import contextmanager, suppress
5
+ from dataclasses import dataclass
4
6
  from itertools import chain
5
7
  from os import chdir
6
8
  from pathlib import Path
7
- from typing import TYPE_CHECKING
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
8
14
 
9
15
  if TYPE_CHECKING:
10
16
  from collections.abc import Iterator, Sequence
11
17
 
12
- from utilities.types import PathLike, PathLikeOrCallable
18
+ from utilities.types import MaybeCallablePathLike, PathLike
13
19
 
14
20
  PWD = Path.cwd()
15
21
 
@@ -25,20 +31,73 @@ def ensure_suffix(path: PathLike, suffix: str, /) -> Path:
25
31
  return path.with_name(name)
26
32
 
27
33
 
28
- def list_dir(path: PathLike, /) -> Sequence[Path]:
29
- """List the contents of a directory."""
30
- return sorted(Path(path).iterdir())
34
+ ##
31
35
 
32
36
 
33
- def resolve_path(*, path: PathLikeOrCallable | None = None) -> Path:
34
- """Resolve for a path."""
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."""
35
45
  match path:
46
+ case Path() | Sentinel():
47
+ return path
48
+ case str():
49
+ return Path(path)
36
50
  case None:
37
51
  return Path.cwd()
38
- case Path() | str():
39
- return Path(path)
40
- case _:
41
- return Path(path())
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
+ ##
42
101
 
43
102
 
44
103
  @contextmanager
@@ -52,4 +111,4 @@ def temp_cwd(path: PathLike, /) -> Iterator[None]:
52
111
  chdir(prev)
53
112
 
54
113
 
55
- __all__ = ["ensure_suffix", "list_dir", "resolve_path", "temp_cwd"]
114
+ __all__ = ["PWD", "ensure_suffix", "get_path", "list_dir", "temp_cwd"]
utilities/pyinstrument.py CHANGED
@@ -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)
utilities/types.py CHANGED
@@ -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",
utilities/version.py CHANGED
@@ -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:
utilities/git.py DELETED
@@ -1,93 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from pathlib import Path
5
- from re import IGNORECASE, search
6
- from subprocess import PIPE, CalledProcessError, check_call, check_output
7
- from typing import TYPE_CHECKING, override
8
-
9
- from utilities.pathlib import PWD
10
-
11
- if TYPE_CHECKING:
12
- from utilities.types import PathLike
13
-
14
-
15
- def fetch_all_tags(*, cwd: PathLike = PWD) -> None:
16
- """Fetch the tags."""
17
- _ = check_call(["git", "fetch", "--all", "--tags"], cwd=cwd)
18
-
19
-
20
- ##
21
-
22
-
23
- def get_branch_name(*, cwd: PathLike = PWD) -> str:
24
- """Get the current branch name."""
25
- output = check_output(
26
- _GIT_REV_PARSE_ABBREV_REV_HEAD, stderr=PIPE, cwd=cwd, text=True
27
- )
28
- return output.strip("\n")
29
-
30
-
31
- _GIT_REV_PARSE_ABBREV_REV_HEAD = ["git", "rev-parse", "--abbrev-ref", "HEAD"]
32
-
33
-
34
- ##
35
-
36
-
37
- def get_ref_tags(ref: str, /, *, cwd: PathLike = PWD) -> list[str]:
38
- """Get the tags of a reference."""
39
- output = check_output([*_GIT_TAG_POINTS_AT, ref], stderr=PIPE, cwd=cwd, text=True)
40
- return output.strip("\n").splitlines()
41
-
42
-
43
- _GIT_TAG_POINTS_AT = ["git", "tag", "--points-at"]
44
-
45
-
46
- ##
47
-
48
-
49
- def get_repo_name(*, cwd: PathLike = PWD) -> str:
50
- """Get the repo name."""
51
- output = check_output(_GIT_REMOTE_GET_URL_ORIGIN, stderr=PIPE, cwd=cwd, text=True)
52
- return Path(output.strip("\n")).stem # not valid_path
53
-
54
-
55
- _GIT_REMOTE_GET_URL_ORIGIN = ["git", "remote", "get-url", "origin"]
56
-
57
-
58
- ##
59
-
60
-
61
- def get_repo_root(*, cwd: PathLike = PWD) -> Path:
62
- """Get the repo root."""
63
- try:
64
- output = check_output(
65
- ["git", "rev-parse", "--show-toplevel"], stderr=PIPE, cwd=cwd, text=True
66
- )
67
- except CalledProcessError as error:
68
- # newer versions of git report "Not a git repository", whilst older
69
- # versions report "not a git repository"
70
- if search("fatal: not a git repository", error.stderr, flags=IGNORECASE):
71
- raise GetRepoRootError(cwd=cwd) from error
72
- raise # pragma: no cover
73
- else:
74
- return Path(output.strip("\n"))
75
-
76
-
77
- @dataclass(kw_only=True, slots=True)
78
- class GetRepoRootError(Exception):
79
- cwd: PathLike
80
-
81
- @override
82
- def __str__(self) -> str:
83
- return f"Path is not part of a `git` repository: {self.cwd}"
84
-
85
-
86
- __all__ = [
87
- "GetRepoRootError",
88
- "fetch_all_tags",
89
- "get_branch_name",
90
- "get_ref_tags",
91
- "get_repo_name",
92
- "get_repo_root",
93
- ]