dycw-utilities 0.129.10__py3-none-any.whl → 0.175.17__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.
Files changed (103) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. dycw_utilities-0.175.17.dist-info/RECORD +103 -0
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.175.17.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +14 -14
  7. utilities/asyncio.py +350 -819
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +77 -22
  10. utilities/cachetools.py +24 -29
  11. utilities/click.py +393 -237
  12. utilities/concurrent.py +8 -11
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +83 -118
  17. utilities/docker.py +293 -0
  18. utilities/enum.py +26 -23
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +29 -65
  21. utilities/fpdf2.py +3 -3
  22. utilities/functions.py +169 -416
  23. utilities/functools.py +18 -19
  24. utilities/git.py +9 -30
  25. utilities/grp.py +28 -0
  26. utilities/gzip.py +31 -0
  27. utilities/http.py +3 -2
  28. utilities/hypothesis.py +738 -589
  29. utilities/importlib.py +17 -1
  30. utilities/inflect.py +25 -0
  31. utilities/iterables.py +194 -262
  32. utilities/jinja2.py +148 -0
  33. utilities/json.py +70 -0
  34. utilities/libcst.py +38 -17
  35. utilities/lightweight_charts.py +5 -9
  36. utilities/logging.py +345 -543
  37. utilities/math.py +18 -13
  38. utilities/memory_profiler.py +11 -15
  39. utilities/more_itertools.py +200 -131
  40. utilities/operator.py +33 -29
  41. utilities/optuna.py +6 -6
  42. utilities/orjson.py +272 -137
  43. utilities/os.py +61 -4
  44. utilities/parse.py +59 -61
  45. utilities/pathlib.py +281 -40
  46. utilities/permissions.py +298 -0
  47. utilities/pickle.py +2 -2
  48. utilities/platform.py +24 -5
  49. utilities/polars.py +1214 -430
  50. utilities/polars_ols.py +1 -1
  51. utilities/postgres.py +408 -0
  52. utilities/pottery.py +113 -26
  53. utilities/pqdm.py +10 -11
  54. utilities/psutil.py +6 -57
  55. utilities/pwd.py +28 -0
  56. utilities/pydantic.py +4 -54
  57. utilities/pydantic_settings.py +240 -0
  58. utilities/pydantic_settings_sops.py +76 -0
  59. utilities/pyinstrument.py +8 -10
  60. utilities/pytest.py +227 -121
  61. utilities/pytest_plugins/__init__.py +1 -0
  62. utilities/pytest_plugins/pytest_randomly.py +23 -0
  63. utilities/pytest_plugins/pytest_regressions.py +56 -0
  64. utilities/pytest_regressions.py +26 -46
  65. utilities/random.py +13 -9
  66. utilities/re.py +58 -28
  67. utilities/redis.py +401 -550
  68. utilities/scipy.py +1 -1
  69. utilities/sentinel.py +10 -0
  70. utilities/shelve.py +4 -1
  71. utilities/shutil.py +25 -0
  72. utilities/slack_sdk.py +36 -106
  73. utilities/sqlalchemy.py +502 -473
  74. utilities/sqlalchemy_polars.py +38 -94
  75. utilities/string.py +2 -3
  76. utilities/subprocess.py +1572 -0
  77. utilities/tempfile.py +86 -4
  78. utilities/testbook.py +50 -0
  79. utilities/text.py +165 -42
  80. utilities/timer.py +37 -65
  81. utilities/traceback.py +158 -929
  82. utilities/types.py +146 -116
  83. utilities/typing.py +531 -71
  84. utilities/tzdata.py +1 -53
  85. utilities/tzlocal.py +6 -23
  86. utilities/uuid.py +43 -5
  87. utilities/version.py +27 -26
  88. utilities/whenever.py +1776 -386
  89. utilities/zoneinfo.py +84 -22
  90. dycw_utilities-0.129.10.dist-info/METADATA +0 -241
  91. dycw_utilities-0.129.10.dist-info/RECORD +0 -96
  92. dycw_utilities-0.129.10.dist-info/WHEEL +0 -4
  93. dycw_utilities-0.129.10.dist-info/licenses/LICENSE +0 -21
  94. utilities/datetime.py +0 -1409
  95. utilities/eventkit.py +0 -402
  96. utilities/loguru.py +0 -144
  97. utilities/luigi.py +0 -228
  98. utilities/period.py +0 -324
  99. utilities/pyrsistent.py +0 -89
  100. utilities/python_dotenv.py +0 -105
  101. utilities/streamlit.py +0 -105
  102. utilities/sys.py +0 -87
  103. utilities/tenacity.py +0 -145
utilities/random.py CHANGED
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from random import Random, SystemRandom
4
- from typing import TYPE_CHECKING, TypeVar
4
+ from typing import TYPE_CHECKING
5
+
6
+ from utilities.functools import cache
5
7
 
6
8
  if TYPE_CHECKING:
7
9
  from collections.abc import Iterable
@@ -9,7 +11,6 @@ if TYPE_CHECKING:
9
11
  from utilities.types import Seed
10
12
 
11
13
 
12
- _T = TypeVar("_T")
13
14
  SYSTEM_RANDOM = SystemRandom()
14
15
 
15
16
 
@@ -18,16 +19,16 @@ SYSTEM_RANDOM = SystemRandom()
18
19
 
19
20
  def bernoulli(*, true: float = 0.5, seed: Seed | None = None) -> bool:
20
21
  """Return a Bernoulli random variate."""
21
- seed = get_state(seed=seed)
22
- return bool(seed.binomialvariate(p=true))
22
+ state = get_state(seed)
23
+ return bool(state.binomialvariate(p=true))
23
24
 
24
25
 
25
26
  ##
26
27
 
27
28
 
28
- def get_docker_name(*, seed: Seed | None = None) -> str:
29
+ def get_docker_name(seed: Seed | None = None, /) -> str:
29
30
  """Get a docker name."""
30
- state = get_state(seed=seed)
31
+ state = get_state(seed)
31
32
  prefix = state.choice(_DOCKER_PREFIXES)
32
33
  suffix = state.choice(_DOCKER_SUFFIXES)
33
34
  digit = state.randint(0, 9)
@@ -48,16 +49,19 @@ _DOCKER_SUFFIXES = [
48
49
  ##
49
50
 
50
51
 
51
- def get_state(*, seed: Seed | None = None) -> Random:
52
+ @cache
53
+ def get_state(seed: Seed | None = None, /) -> Random:
52
54
  """Get a random state."""
53
55
  return seed if isinstance(seed, Random) else Random(x=seed)
54
56
 
55
57
 
56
58
  ##
57
- def shuffle(iterable: Iterable[_T], /, *, seed: Seed | None = None) -> list[_T]:
59
+
60
+
61
+ def shuffle[T](iterable: Iterable[T], /, *, seed: Seed | None = None) -> list[T]:
58
62
  """Shuffle an iterable."""
59
63
  copy = list(iterable).copy()
60
- state = get_state(seed=seed)
64
+ state = get_state(seed)
61
65
  state.shuffle(copy)
62
66
  return copy
63
67
 
utilities/re.py CHANGED
@@ -2,37 +2,58 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from dataclasses import dataclass
5
- from typing import override
5
+ from re import Pattern
6
+ from typing import TYPE_CHECKING, assert_never, override
6
7
 
7
- from utilities.iterables import OneEmptyError, OneNonUniqueError, one
8
+ if TYPE_CHECKING:
9
+ from utilities.types import PatternLike
8
10
 
9
11
 
10
- def extract_group(pattern: str, text: str, /, *, flags: int = 0) -> str:
12
+ def ensure_pattern(pattern: PatternLike, /, *, flags: int = 0) -> Pattern[str]:
13
+ """Ensure a pattern is returned."""
14
+ match pattern:
15
+ case Pattern():
16
+ return pattern
17
+ case str():
18
+ return re.compile(pattern, flags=flags)
19
+ case never:
20
+ assert_never(never)
21
+
22
+
23
+ ##
24
+
25
+
26
+ def extract_group(pattern: PatternLike, text: str, /, *, flags: int = 0) -> str:
11
27
  """Extract a group.
12
28
 
13
29
  The regex must have 1 capture group, and this must match exactly once.
14
30
  """
15
- compiled = re.compile(pattern, flags=flags)
16
- match compiled.groups:
31
+ pattern_use = ensure_pattern(pattern, flags=flags)
32
+ match pattern_use.groups:
17
33
  case 0:
18
- raise _ExtractGroupNoCaptureGroupsError(pattern=pattern, text=text)
34
+ raise _ExtractGroupNoCaptureGroupsError(pattern=pattern_use, text=text)
19
35
  case 1:
20
- matches: list[str] = compiled.findall(text)
21
- try:
22
- return one(matches)
23
- except OneEmptyError:
24
- raise _ExtractGroupNoMatchesError(pattern=pattern, text=text) from None
25
- except OneNonUniqueError:
26
- raise _ExtractGroupMultipleMatchesError(
27
- pattern=pattern, text=text, matches=matches
28
- ) from None
36
+ matches: list[str] = pattern_use.findall(text)
37
+ match len(matches):
38
+ case 0:
39
+ raise _ExtractGroupNoMatchesError(
40
+ pattern=pattern_use, text=text
41
+ ) from None
42
+ case 1:
43
+ return matches[0]
44
+ case _:
45
+ raise _ExtractGroupMultipleMatchesError(
46
+ pattern=pattern_use, text=text, matches=matches
47
+ ) from None
29
48
  case _:
30
- raise _ExtractGroupMultipleCaptureGroupsError(pattern=pattern, text=text)
49
+ raise _ExtractGroupMultipleCaptureGroupsError(
50
+ pattern=pattern_use, text=text
51
+ )
31
52
 
32
53
 
33
54
  @dataclass(kw_only=True, slots=True)
34
55
  class ExtractGroupError(Exception):
35
- pattern: str
56
+ pattern: Pattern[str]
36
57
  text: str
37
58
 
38
59
 
@@ -68,32 +89,35 @@ class _ExtractGroupNoMatchesError(ExtractGroupError):
68
89
  return f"Pattern {self.pattern} must match against {self.text}"
69
90
 
70
91
 
71
- def extract_groups(pattern: str, text: str, /, *, flags: int = 0) -> list[str]:
92
+ ##
93
+
94
+
95
+ def extract_groups(pattern: PatternLike, text: str, /, *, flags: int = 0) -> list[str]:
72
96
  """Extract multiple groups.
73
97
 
74
98
  The regex may have any number of capture groups, and they must collectively
75
99
  match exactly once.
76
100
  """
77
- compiled = re.compile(pattern, flags=flags)
78
- if (n_groups := compiled.groups) == 0:
79
- raise _ExtractGroupsNoCaptureGroupsError(pattern=pattern, text=text)
80
- matches: list[str] = compiled.findall(text)
101
+ pattern_use = ensure_pattern(pattern, flags=flags)
102
+ if (n_groups := pattern_use.groups) == 0:
103
+ raise _ExtractGroupsNoCaptureGroupsError(pattern=pattern_use, text=text)
104
+ matches: list[str] = pattern_use.findall(text)
81
105
  match len(matches), n_groups:
82
106
  case 0, _:
83
- raise _ExtractGroupsNoMatchesError(pattern=pattern, text=text)
107
+ raise _ExtractGroupsNoMatchesError(pattern=pattern_use, text=text)
84
108
  case 1, 1:
85
109
  return matches
86
110
  case 1, _:
87
- return list(one(matches))
111
+ return list(matches[0])
88
112
  case _:
89
113
  raise _ExtractGroupsMultipleMatchesError(
90
- pattern=pattern, text=text, matches=matches
114
+ pattern=pattern_use, text=text, matches=matches
91
115
  )
92
116
 
93
117
 
94
118
  @dataclass(kw_only=True, slots=True)
95
119
  class ExtractGroupsError(Exception):
96
- pattern: str
120
+ pattern: Pattern[str]
97
121
  text: str
98
122
 
99
123
 
@@ -108,7 +132,7 @@ class _ExtractGroupsMultipleMatchesError(ExtractGroupsError):
108
132
 
109
133
  @dataclass(kw_only=True, slots=True)
110
134
  class _ExtractGroupsNoCaptureGroupsError(ExtractGroupsError):
111
- pattern: str
135
+ pattern: Pattern[str]
112
136
  text: str
113
137
 
114
138
  @override
@@ -123,4 +147,10 @@ class _ExtractGroupsNoMatchesError(ExtractGroupsError):
123
147
  return f"Pattern {self.pattern} must match against {self.text}"
124
148
 
125
149
 
126
- __all__ = ["ExtractGroupError", "ExtractGroupsError", "extract_group", "extract_groups"]
150
+ __all__ = [
151
+ "ExtractGroupError",
152
+ "ExtractGroupsError",
153
+ "ensure_pattern",
154
+ "extract_group",
155
+ "extract_groups",
156
+ ]