dycw-utilities 0.174.10__tar.gz → 0.174.12__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 (103) hide show
  1. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/PKG-INFO +1 -1
  2. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/pyproject.toml +2 -2
  3. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/__init__.py +1 -1
  4. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/permissions.py +92 -59
  5. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/README.md +0 -0
  6. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/aeventkit.py +0 -0
  7. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/altair.py +0 -0
  8. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/asyncio.py +0 -0
  9. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/atomicwrites.py +0 -0
  10. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/atools.py +0 -0
  11. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/cachetools.py +0 -0
  12. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/click.py +0 -0
  13. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/concurrent.py +0 -0
  14. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/contextlib.py +0 -0
  15. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/contextvars.py +0 -0
  16. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/cryptography.py +0 -0
  17. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/cvxpy.py +0 -0
  18. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/dataclasses.py +0 -0
  19. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/docker.py +0 -0
  20. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/enum.py +0 -0
  21. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/errors.py +0 -0
  22. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/fastapi.py +0 -0
  23. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/fpdf2.py +0 -0
  24. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/functions.py +0 -0
  25. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/functools.py +0 -0
  26. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/getpass.py +0 -0
  27. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/git.py +0 -0
  28. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/grp.py +0 -0
  29. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/gzip.py +0 -0
  30. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/hashlib.py +0 -0
  31. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/http.py +0 -0
  32. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/hypothesis.py +0 -0
  33. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/importlib.py +0 -0
  34. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/inflect.py +0 -0
  35. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/ipython.py +0 -0
  36. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/iterables.py +0 -0
  37. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/jinja2.py +0 -0
  38. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/json.py +0 -0
  39. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/jupyter.py +0 -0
  40. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/libcst.py +0 -0
  41. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/lightweight_charts.py +0 -0
  42. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/logging.py +0 -0
  43. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/math.py +0 -0
  44. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/memory_profiler.py +0 -0
  45. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/modules.py +0 -0
  46. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/more_itertools.py +0 -0
  47. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/numpy.py +0 -0
  48. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/operator.py +0 -0
  49. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/optuna.py +0 -0
  50. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/orjson.py +0 -0
  51. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/os.py +0 -0
  52. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/parse.py +0 -0
  53. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pathlib.py +0 -0
  54. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pickle.py +0 -0
  55. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/platform.py +0 -0
  56. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/polars.py +0 -0
  57. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/polars_ols.py +0 -0
  58. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/postgres.py +0 -0
  59. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pottery.py +0 -0
  60. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pqdm.py +0 -0
  61. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/psutil.py +0 -0
  62. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pwd.py +0 -0
  63. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/py.typed +0 -0
  64. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pydantic.py +0 -0
  65. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pydantic_settings.py +0 -0
  66. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pydantic_settings_sops.py +0 -0
  67. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pyinstrument.py +0 -0
  68. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pytest.py +0 -0
  69. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pytest_plugins/__init__.py +0 -0
  70. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  71. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  72. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/pytest_regressions.py +0 -0
  73. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/random.py +0 -0
  74. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/re.py +0 -0
  75. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/redis.py +0 -0
  76. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/reprlib.py +0 -0
  77. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/scipy.py +0 -0
  78. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/sentinel.py +0 -0
  79. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/shelve.py +0 -0
  80. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/shutil.py +0 -0
  81. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/slack_sdk.py +0 -0
  82. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/socket.py +0 -0
  83. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/sqlalchemy.py +0 -0
  84. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/sqlalchemy_polars.py +0 -0
  85. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/statsmodels.py +0 -0
  86. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/string.py +0 -0
  87. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/subprocess.py +0 -0
  88. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/tempfile.py +0 -0
  89. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/testbook.py +0 -0
  90. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/text.py +0 -0
  91. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/threading.py +0 -0
  92. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/timer.py +0 -0
  93. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/traceback.py +0 -0
  94. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/types.py +0 -0
  95. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/typing.py +0 -0
  96. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/tzdata.py +0 -0
  97. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/tzlocal.py +0 -0
  98. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/uuid.py +0 -0
  99. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/version.py +0 -0
  100. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/warnings.py +0 -0
  101. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/whenever.py +0 -0
  102. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/zipfile.py +0 -0
  103. {dycw_utilities-0.174.10 → dycw_utilities-0.174.12}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dycw-utilities
3
- Version: 0.174.10
3
+ Version: 0.174.12
4
4
  Author: Derek Wan
5
5
  Author-email: Derek Wan <d.wan@icloud.com>
6
6
  Requires-Dist: atomicwrites>=1.4.1,<1.5
@@ -101,7 +101,7 @@
101
101
  name = "dycw-utilities"
102
102
  readme = "README.md"
103
103
  requires-python = ">= 3.12"
104
- version = "0.174.10"
104
+ version = "0.174.12"
105
105
 
106
106
  [project.entry-points.pytest11]
107
107
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -135,7 +135,7 @@
135
135
  # bump-my-version
136
136
  [tool.bumpversion]
137
137
  allow_dirty = true
138
- current_version = "0.174.10"
138
+ current_version = "0.174.12"
139
139
 
140
140
  [[tool.bumpversion.files]]
141
141
  filename = "src/utilities/__init__.py"
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.174.10"
3
+ __version__ = "0.174.12"
@@ -3,7 +3,9 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from functools import reduce
5
5
  from operator import or_
6
+ from pathlib import Path
6
7
  from stat import (
8
+ S_IMODE,
7
9
  S_IRGRP,
8
10
  S_IROTH,
9
11
  S_IRUSR,
@@ -14,15 +16,41 @@ from stat import (
14
16
  S_IXOTH,
15
17
  S_IXUSR,
16
18
  )
17
- from typing import Literal, Self, override
19
+ from typing import TYPE_CHECKING, Literal, Self, assert_never, override
18
20
 
19
21
  from utilities.dataclasses import replace_non_sentinel
20
22
  from utilities.re import ExtractGroupsError, extract_groups
21
23
  from utilities.sentinel import Sentinel, sentinel
22
24
 
25
+ if TYPE_CHECKING:
26
+ from utilities.types import PathLike
27
+
28
+ type PermissionsLike = Permissions | int | str
29
+
30
+
31
+ ##
32
+
33
+
34
+ def ensure_perms(perms: PermissionsLike, /) -> Permissions:
35
+ """Ensure a set of file permissions."""
36
+ match perms:
37
+ case Permissions():
38
+ return perms
39
+ case int():
40
+ return Permissions.from_int(perms)
41
+ case str():
42
+ return Permissions.from_text(perms)
43
+ case never:
44
+ assert_never(never)
45
+
46
+
47
+ ##
48
+
23
49
 
24
50
  @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
25
51
  class Permissions:
52
+ """A set of file permissions."""
53
+
26
54
  user_read: bool = False
27
55
  user_write: bool = False
28
56
  user_execute: bool = False
@@ -34,26 +62,18 @@ class Permissions:
34
62
  others_execute: bool = False
35
63
 
36
64
  def __int__(self) -> int:
37
- return (
38
- 100
39
- * self._int(
40
- read=self.user_read, write=self.user_write, execute=self.user_execute
41
- )
42
- + 10
43
- * self._int(
44
- read=self.group_read, write=self.group_write, execute=self.group_execute
45
- )
46
- + self._int(
47
- read=self.others_read,
48
- write=self.others_write,
49
- execute=self.others_execute,
50
- )
51
- )
52
-
53
- def _int(
54
- self, *, read: bool = False, write: bool = False, execute: bool = False
55
- ) -> int:
56
- return (4 if read else 0) + (2 if write else 0) + (1 if execute else 0)
65
+ flags: list[int] = [
66
+ S_IRUSR if self.user_read else 0,
67
+ S_IWUSR if self.user_write else 0,
68
+ S_IXUSR if self.user_execute else 0,
69
+ S_IRGRP if self.group_read else 0,
70
+ S_IWGRP if self.group_write else 0,
71
+ S_IXGRP if self.group_execute else 0,
72
+ S_IROTH if self.others_read else 0,
73
+ S_IWOTH if self.others_write else 0,
74
+ S_IXOTH if self.others_execute else 0,
75
+ ]
76
+ return reduce(or_, flags)
57
77
 
58
78
  @override
59
79
  def __repr__(self) -> str:
@@ -87,13 +107,11 @@ class Permissions:
87
107
  write: bool = False,
88
108
  execute: bool = False,
89
109
  ) -> str:
90
- parts: list[str] = []
91
- if read:
92
- parts.append("r")
93
- if write:
94
- parts.append("w")
95
- if execute:
96
- parts.append("x")
110
+ parts: list[str] = [
111
+ "r" if read else "",
112
+ "w" if write else "",
113
+ "x" if execute else "",
114
+ ]
97
115
  return f"{prefix}={''.join(parts)}"
98
116
 
99
117
  @override
@@ -101,12 +119,12 @@ class Permissions:
101
119
  return repr(self)
102
120
 
103
121
  @classmethod
104
- def from_int(cls, n: int, /) -> Self:
122
+ def from_human_int(cls, n: int, /) -> Self:
105
123
  if not (0 <= n <= 777):
106
- raise PermissionsFromIntRangeError(n=n)
107
- user_read, user_write, user_execute = cls._from_int(n, (n // 100) % 10)
108
- group_read, group_write, group_execute = cls._from_int(n, (n // 10) % 10)
109
- others_read, others_write, others_execute = cls._from_int(n, n % 10)
124
+ raise PermissionsFromHumanIntRangeError(n=n)
125
+ user_read, user_write, user_execute = cls._from_human_int(n, (n // 100) % 10)
126
+ group_read, group_write, group_execute = cls._from_human_int(n, (n // 10) % 10)
127
+ others_read, others_write, others_execute = cls._from_human_int(n, n % 10)
110
128
  return cls(
111
129
  user_read=user_read,
112
130
  user_write=user_write,
@@ -120,13 +138,13 @@ class Permissions:
120
138
  )
121
139
 
122
140
  @classmethod
123
- def _from_int(cls, n: int, digit: int, /) -> tuple[bool, bool, bool]:
141
+ def _from_human_int(cls, n: int, digit: int, /) -> tuple[bool, bool, bool]:
124
142
  if not (0 <= digit <= 7):
125
- raise PermissionsFromIntDigitError(n=n, digit=digit)
143
+ raise PermissionsFromHumanIntDigitError(n=n, digit=digit)
126
144
  return bool(4 & digit), bool(2 & digit), bool(1 & digit)
127
145
 
128
146
  @classmethod
129
- def from_octal(cls, n: int, /) -> Self:
147
+ def from_int(cls, n: int, /) -> Self:
130
148
  if 0o0 <= n <= 0o777:
131
149
  return cls(
132
150
  user_read=bool(n & S_IRUSR),
@@ -139,7 +157,11 @@ class Permissions:
139
157
  others_write=bool(n & S_IWOTH),
140
158
  others_execute=bool(n & S_IXOTH),
141
159
  )
142
- raise PermissionsFromOctalError(n=n)
160
+ raise PermissionsFromIntError(n=n)
161
+
162
+ @classmethod
163
+ def from_path(cls, path: PathLike, /) -> Self:
164
+ return cls.from_int(S_IMODE(Path(path).stat().st_mode))
143
165
 
144
166
  @classmethod
145
167
  def from_text(cls, text: str, /) -> Self:
@@ -170,19 +192,27 @@ class Permissions:
170
192
  return read != "", write != "", execute != ""
171
193
 
172
194
  @property
173
- def octal(self) -> int:
174
- flags: list[int] = [
175
- S_IRUSR if self.user_read else 0,
176
- S_IWUSR if self.user_write else 0,
177
- S_IXUSR if self.user_execute else 0,
178
- S_IRGRP if self.group_read else 0,
179
- S_IWGRP if self.group_write else 0,
180
- S_IXGRP if self.group_execute else 0,
181
- S_IROTH if self.others_read else 0,
182
- S_IWOTH if self.others_write else 0,
183
- S_IXOTH if self.others_execute else 0,
184
- ]
185
- return reduce(or_, flags)
195
+ def human_int(self) -> int:
196
+ return (
197
+ 100
198
+ * self._human_int(
199
+ read=self.user_read, write=self.user_write, execute=self.user_execute
200
+ )
201
+ + 10
202
+ * self._human_int(
203
+ read=self.group_read, write=self.group_write, execute=self.group_execute
204
+ )
205
+ + self._human_int(
206
+ read=self.others_read,
207
+ write=self.others_write,
208
+ execute=self.others_execute,
209
+ )
210
+ )
211
+
212
+ def _human_int(
213
+ self, *, read: bool = False, write: bool = False, execute: bool = False
214
+ ) -> int:
215
+ return (4 if read else 0) + (2 if write else 0) + (1 if execute else 0)
186
216
 
187
217
  def replace(
188
218
  self,
@@ -216,33 +246,35 @@ class PermissionsError(Exception): ...
216
246
 
217
247
 
218
248
  @dataclass(kw_only=True, slots=True)
219
- class PermissionsFromIntError(PermissionsError):
249
+ class PermissionsFromHumanIntError(PermissionsError):
220
250
  n: int
221
251
 
222
252
 
223
253
  @dataclass(kw_only=True, slots=True)
224
- class PermissionsFromIntRangeError(PermissionsFromIntError):
254
+ class PermissionsFromHumanIntRangeError(PermissionsFromHumanIntError):
225
255
  @override
226
256
  def __str__(self) -> str:
227
- return f"Invalid integer for permissions; got {self.n}"
257
+ return f"Invalid human integer for permissions; got {self.n}"
228
258
 
229
259
 
230
260
  @dataclass(kw_only=True, slots=True)
231
- class PermissionsFromIntDigitError(PermissionsFromIntError):
261
+ class PermissionsFromHumanIntDigitError(PermissionsFromHumanIntError):
232
262
  digit: int
233
263
 
234
264
  @override
235
265
  def __str__(self) -> str:
236
- return f"Invalid integer for permissions; got digit {self.digit} in {self.n}"
266
+ return (
267
+ f"Invalid human integer for permissions; got digit {self.digit} in {self.n}"
268
+ )
237
269
 
238
270
 
239
271
  @dataclass(kw_only=True, slots=True)
240
- class PermissionsFromOctalError(PermissionsError):
272
+ class PermissionsFromIntError(PermissionsError):
241
273
  n: int
242
274
 
243
275
  @override
244
276
  def __str__(self) -> str:
245
- return f"Invalid octal for permissions; got {oct(self.n)}"
277
+ return f"Invalid integer for permissions; got {self.n} = {oct(self.n)}"
246
278
 
247
279
 
248
280
  @dataclass(kw_only=True, slots=True)
@@ -257,8 +289,9 @@ class PermissionsFromTextError(PermissionsError):
257
289
  __all__ = [
258
290
  "Permissions",
259
291
  "PermissionsError",
260
- "PermissionsFromIntDigitError",
292
+ "PermissionsFromHumanIntDigitError",
293
+ "PermissionsFromHumanIntError",
261
294
  "PermissionsFromIntError",
262
- "PermissionsFromOctalError",
263
295
  "PermissionsFromTextError",
296
+ "ensure_perms",
264
297
  ]