dycw-utilities 0.174.8__py3-none-any.whl → 0.174.9__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.3
2
2
  Name: dycw-utilities
3
- Version: 0.174.8
3
+ Version: 0.174.9
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
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=n95VfhS7rnwwdfXlNQHT0VSWZoAJAyyrDnWLplcWMZ8,60
1
+ utilities/__init__.py,sha256=s0yfL-qs6UI6ltNUkBKltAzsUuIudzaPhHhfNI2ILBI,60
2
2
  utilities/aeventkit.py,sha256=OmDBhYGgbsKrB7cdC5FFpJHUatX9O76eTeKVVTksp2Y,12673
3
3
  utilities/altair.py,sha256=rUK99g9x6CYDDfiZrf-aTx5fSRbL1Q8ctgKORowzXHg,9060
4
4
  utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
@@ -25,7 +25,7 @@ utilities/grp.py,sha256=1vV3gNR9dQsl1vtUtvC_2qgVdQzm7O8lLMSh56cTbeg,694
25
25
  utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
26
26
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
27
27
  utilities/http.py,sha256=TsavEfHlRtlLaeV21Z6KZh0qbPw-kvD1zsQdZ7Kep5Q,977
28
- utilities/hypothesis.py,sha256=uRgpkHhDOx9hEaOs7SyAuVMARCFo86VxlP72-jJCOAU,45471
28
+ utilities/hypothesis.py,sha256=wk1HiNdBg7tGPEKLZ5uiNVbtlSZl58QJjlediYoSHkA,46753
29
29
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
30
30
  utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
31
31
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
@@ -47,6 +47,7 @@ utilities/orjson.py,sha256=T_0SlK811ysg46d3orvIPY3JpBa4FRMpP2wlPQo7-gU,41854
47
47
  utilities/os.py,sha256=kjKKSQfnRqFTTZ315iavaaGd3gGuYNoSWlxVLCJjyQs,4852
48
48
  utilities/parse.py,sha256=g7Qm9eBOIeDId2tGA021CIaeF6jp1TI8rx4srdvlyoo,17937
49
49
  utilities/pathlib.py,sha256=EKZn-wWxH7MEWFrQGqHIoB-GJzyXeiEj8iDIgvkr8Wk,9325
50
+ utilities/permissions.py,sha256=PlY95KIon6onivvZt8H1dKlzPXatLdZ5cUPdfwZDfs4,9408
50
51
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
51
52
  utilities/platform.py,sha256=0pYO5v7L2sU5UN87zHhEEhTKsZ9NIEM8N6UCr0F7bLY,2778
52
53
  utilities/polars.py,sha256=cNFBLWgOMUAp_Sz4xtlto17uZswZRrcfQYC95QKyaY4,87483
@@ -97,7 +98,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
97
98
  utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
98
99
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
99
100
  utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
100
- dycw_utilities-0.174.8.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
101
- dycw_utilities-0.174.8.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
102
- dycw_utilities-0.174.8.dist-info/METADATA,sha256=Y6jmazzTFERRUb55Cbl5cwt3sCw5yThYY7Rrcn1llw0,1709
103
- dycw_utilities-0.174.8.dist-info/RECORD,,
101
+ dycw_utilities-0.174.9.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
102
+ dycw_utilities-0.174.9.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
103
+ dycw_utilities-0.174.9.dist-info/METADATA,sha256=LpxiNDUuPoCbqx1Jb4H4UsVRdjGHNrGyeslCZqKy2AE,1709
104
+ dycw_utilities-0.174.9.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.174.8"
3
+ __version__ = "0.174.9"
utilities/hypothesis.py CHANGED
@@ -77,6 +77,7 @@ from utilities.math import (
77
77
  )
78
78
  from utilities.os import get_env_var
79
79
  from utilities.pathlib import module_path, temp_cwd
80
+ from utilities.permissions import Permissions
80
81
  from utilities.platform import IS_LINUX
81
82
  from utilities.sentinel import Sentinel, is_sentinel, sentinel
82
83
  from utilities.tempfile import TEMP_DIR, TemporaryDirectory
@@ -864,6 +865,38 @@ def _path_parts(draw: DrawFn, /) -> str:
864
865
  ##
865
866
 
866
867
 
868
+ @composite
869
+ def permissions(
870
+ draw: DrawFn,
871
+ /,
872
+ *,
873
+ user_read: MaybeSearchStrategy[bool | None] = None,
874
+ user_write: MaybeSearchStrategy[bool | None] = None,
875
+ user_execute: MaybeSearchStrategy[bool | None] = None,
876
+ group_read: MaybeSearchStrategy[bool | None] = None,
877
+ group_write: MaybeSearchStrategy[bool | None] = None,
878
+ group_execute: MaybeSearchStrategy[bool | None] = None,
879
+ others_read: MaybeSearchStrategy[bool | None] = None,
880
+ others_write: MaybeSearchStrategy[bool | None] = None,
881
+ others_execute: MaybeSearchStrategy[bool | None] = None,
882
+ ) -> Permissions:
883
+ """Strategy for generating `Permissions`."""
884
+ return Permissions(
885
+ user_read=draw2(draw, user_read, booleans()),
886
+ user_write=draw2(draw, user_write, booleans()),
887
+ user_execute=draw2(draw, user_execute, booleans()),
888
+ group_read=draw2(draw, group_read, booleans()),
889
+ group_write=draw2(draw, group_write, booleans()),
890
+ group_execute=draw2(draw, group_execute, booleans()),
891
+ others_read=draw2(draw, others_read, booleans()),
892
+ others_write=draw2(draw, others_write, booleans()),
893
+ others_execute=draw2(draw, others_execute, booleans()),
894
+ )
895
+
896
+
897
+ ##
898
+
899
+
867
900
  @composite
868
901
  def plain_date_times(
869
902
  draw: DrawFn,
@@ -1611,6 +1644,7 @@ __all__ = [
1611
1644
  "numbers",
1612
1645
  "pairs",
1613
1646
  "paths",
1647
+ "permissions",
1614
1648
  "plain_date_times",
1615
1649
  "py_datetimes",
1616
1650
  "quadruples",
@@ -0,0 +1,297 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from dataclasses import dataclass
5
+ from functools import reduce
6
+ from operator import or_
7
+ from stat import (
8
+ S_IRGRP,
9
+ S_IROTH,
10
+ S_IRUSR,
11
+ S_IWGRP,
12
+ S_IWOTH,
13
+ S_IWUSR,
14
+ S_IXGRP,
15
+ S_IXOTH,
16
+ S_IXUSR,
17
+ )
18
+ from typing import Literal, Self, override
19
+
20
+ from utilities.dataclasses import replace_non_sentinel
21
+ from utilities.functions import ensure_member
22
+ from utilities.re import (
23
+ ExtractGroupError,
24
+ ExtractGroupsError,
25
+ extract_group,
26
+ extract_groups,
27
+ )
28
+ from utilities.sentinel import Sentinel, sentinel
29
+ from utilities.typing import get_args
30
+
31
+ _MIN_INT = 0o0
32
+ _MAX_INT = 0o777
33
+ type _ZeroToSeven = Literal[0, 1, 2, 3, 4, 5, 6, 7]
34
+ _ZERO_TO_SEVEN: list[_ZeroToSeven] = list(get_args(_ZeroToSeven.__value__))
35
+
36
+
37
+ @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
38
+ class Permissions:
39
+ user_read: bool = False
40
+ user_write: bool = False
41
+ user_execute: bool = False
42
+ group_read: bool = False
43
+ group_write: bool = False
44
+ group_execute: bool = False
45
+ others_read: bool = False
46
+ others_write: bool = False
47
+ others_execute: bool = False
48
+
49
+ def __int__(self) -> int:
50
+ return (
51
+ 100
52
+ * self._int(
53
+ read=self.user_read, write=self.user_write, execute=self.user_execute
54
+ )
55
+ + 10
56
+ * self._int(
57
+ read=self.group_read, write=self.group_write, execute=self.group_execute
58
+ )
59
+ + self._int(
60
+ read=self.others_read,
61
+ write=self.others_write,
62
+ execute=self.others_execute,
63
+ )
64
+ )
65
+
66
+ def _int(
67
+ self, *, read: bool = False, write: bool = False, execute: bool = False
68
+ ) -> _ZeroToSeven:
69
+ return (4 if read else 0) + (2 if write else 0) + (1 if execute else 0)
70
+
71
+ @override
72
+ def __repr__(self) -> str:
73
+ return ",".join([
74
+ self._repr_parts(
75
+ "u",
76
+ read=self.user_read,
77
+ write=self.user_write,
78
+ execute=self.user_execute,
79
+ ),
80
+ self._repr_parts(
81
+ "g",
82
+ read=self.group_read,
83
+ write=self.group_write,
84
+ execute=self.group_execute,
85
+ ),
86
+ self._repr_parts(
87
+ "o",
88
+ read=self.others_read,
89
+ write=self.others_write,
90
+ execute=self.others_execute,
91
+ ),
92
+ ])
93
+
94
+ def _repr_parts(
95
+ self,
96
+ prefix: Literal["u", "g", "o"],
97
+ /,
98
+ *,
99
+ read: bool = False,
100
+ write: bool = False,
101
+ execute: bool = False,
102
+ ) -> str:
103
+ parts: list[str] = []
104
+ if read:
105
+ parts.append("r")
106
+ if write:
107
+ parts.append("w")
108
+ if execute:
109
+ parts.append("x")
110
+ return f"{prefix}={''.join(parts)}"
111
+
112
+ @override
113
+ def __str__(self) -> str:
114
+ return repr(self)
115
+
116
+ @classmethod
117
+ def from_int(cls, n: int, /) -> Self:
118
+ with suppress(ExtractGroupsError):
119
+ user, group, others = extract_groups(r"^([0-7])([0-7])([0-7])$", str(n))
120
+ user_read, user_write, user_execute = cls._from_int(
121
+ ensure_member(int(user), _ZERO_TO_SEVEN)
122
+ )
123
+ group_read, group_write, group_execute = cls._from_int(
124
+ ensure_member(int(group), _ZERO_TO_SEVEN)
125
+ )
126
+ others_read, others_write, others_execute = cls._from_int(
127
+ ensure_member(int(others), _ZERO_TO_SEVEN)
128
+ )
129
+ return cls(
130
+ user_read=user_read,
131
+ user_write=user_write,
132
+ user_execute=user_execute,
133
+ group_read=group_read,
134
+ group_write=group_write,
135
+ group_execute=group_execute,
136
+ others_read=others_read,
137
+ others_write=others_write,
138
+ others_execute=others_execute,
139
+ )
140
+ with suppress(ExtractGroupsError):
141
+ group, others = extract_groups(r"^([0-7])([0-7])$", str(n))
142
+ group_read, group_write, group_execute = cls._from_int(
143
+ ensure_member(int(group), _ZERO_TO_SEVEN)
144
+ )
145
+ others_read, others_write, others_execute = cls._from_int(
146
+ ensure_member(int(others), _ZERO_TO_SEVEN)
147
+ )
148
+ return cls(
149
+ group_read=group_read,
150
+ group_write=group_write,
151
+ group_execute=group_execute,
152
+ others_read=others_read,
153
+ others_write=others_write,
154
+ others_execute=others_execute,
155
+ )
156
+ with suppress(ExtractGroupError):
157
+ others = extract_group(r"^([0-7])$", str(n))
158
+ others_read, others_write, others_execute = cls._from_int(
159
+ ensure_member(int(others), _ZERO_TO_SEVEN)
160
+ )
161
+ return cls(
162
+ others_read=others_read,
163
+ others_write=others_write,
164
+ others_execute=others_execute,
165
+ )
166
+ if n == 0:
167
+ return cls()
168
+ raise PermissionsFromIntError(n=n)
169
+
170
+ @classmethod
171
+ def _from_int(cls, n: _ZeroToSeven, /) -> tuple[bool, bool, bool]:
172
+ return bool(4 & n), bool(2 & n), bool(1 & n)
173
+
174
+ @classmethod
175
+ def from_octal(cls, n: int, /) -> Self:
176
+ if _MIN_INT <= n <= _MAX_INT:
177
+ return cls(
178
+ user_read=bool(n & S_IRUSR),
179
+ user_write=bool(n & S_IWUSR),
180
+ user_execute=bool(n & S_IXUSR),
181
+ group_read=bool(n & S_IRGRP),
182
+ group_write=bool(n & S_IWGRP),
183
+ group_execute=bool(n & S_IXGRP),
184
+ others_read=bool(n & S_IROTH),
185
+ others_write=bool(n & S_IWOTH),
186
+ others_execute=bool(n & S_IXOTH),
187
+ )
188
+ raise PermissionsFromOctalError(n=n)
189
+
190
+ @classmethod
191
+ def from_text(cls, text: str, /) -> Self:
192
+ try:
193
+ user, group, others = extract_groups(
194
+ r"^u=(r?w?x?),g=(r?w?x?),o=(r?w?x?)$", text
195
+ )
196
+ except ExtractGroupsError:
197
+ raise PermissionsFromTextError(text=text) from None
198
+ user_read, user_write, user_execute = cls._from_text_part(user)
199
+ group_read, group_write, group_execute = cls._from_text_part(group)
200
+ others_read, others_write, others_execute = cls._from_text_part(others)
201
+ return cls(
202
+ user_read=user_read,
203
+ user_write=user_write,
204
+ user_execute=user_execute,
205
+ group_read=group_read,
206
+ group_write=group_write,
207
+ group_execute=group_execute,
208
+ others_read=others_read,
209
+ others_write=others_write,
210
+ others_execute=others_execute,
211
+ )
212
+
213
+ @classmethod
214
+ def _from_text_part(cls, text: str, /) -> tuple[bool, bool, bool]:
215
+ read, write, execute = extract_groups("^(r?)(w?)(x?)$", text)
216
+ return read != "", write != "", execute != ""
217
+
218
+ @property
219
+ def octal(self) -> int:
220
+ flags: list[int] = [
221
+ S_IRUSR if self.user_read else 0,
222
+ S_IWUSR if self.user_write else 0,
223
+ S_IXUSR if self.user_execute else 0,
224
+ S_IRGRP if self.group_read else 0,
225
+ S_IWGRP if self.group_write else 0,
226
+ S_IXGRP if self.group_execute else 0,
227
+ S_IROTH if self.others_read else 0,
228
+ S_IWOTH if self.others_write else 0,
229
+ S_IXOTH if self.others_execute else 0,
230
+ ]
231
+ return reduce(or_, flags)
232
+
233
+ def replace(
234
+ self,
235
+ *,
236
+ user_read: bool | Sentinel = sentinel,
237
+ user_write: bool | Sentinel = sentinel,
238
+ user_execute: bool | Sentinel = sentinel,
239
+ group_read: bool | Sentinel = sentinel,
240
+ group_write: bool | Sentinel = sentinel,
241
+ group_execute: bool | Sentinel = sentinel,
242
+ others_read: bool | Sentinel = sentinel,
243
+ others_write: bool | Sentinel = sentinel,
244
+ others_execute: bool | Sentinel = sentinel,
245
+ ) -> Self:
246
+ return replace_non_sentinel(
247
+ self,
248
+ user_read=user_read,
249
+ user_write=user_write,
250
+ user_execute=user_execute,
251
+ group_read=group_read,
252
+ group_write=group_write,
253
+ group_execute=group_execute,
254
+ others_read=others_read,
255
+ others_write=others_write,
256
+ others_execute=others_execute,
257
+ )
258
+
259
+
260
+ @dataclass(kw_only=True, slots=True)
261
+ class PermissionsError(Exception): ...
262
+
263
+
264
+ @dataclass(kw_only=True, slots=True)
265
+ class PermissionsFromIntError(PermissionsError):
266
+ n: int
267
+
268
+ @override
269
+ def __str__(self) -> str:
270
+ return f"Invalid integer for permissions; got {self.n}"
271
+
272
+
273
+ @dataclass(kw_only=True, slots=True)
274
+ class PermissionsFromOctalError(PermissionsError):
275
+ n: int
276
+
277
+ @override
278
+ def __str__(self) -> str:
279
+ return f"Invalid octal for permissions; got {oct(self.n)}"
280
+
281
+
282
+ @dataclass(kw_only=True, slots=True)
283
+ class PermissionsFromTextError(PermissionsError):
284
+ text: str
285
+
286
+ @override
287
+ def __str__(self) -> str:
288
+ return f"Invalid string for permissions; got {self.text!r}"
289
+
290
+
291
+ __all__ = [
292
+ "Permissions",
293
+ "PermissionsError",
294
+ "PermissionsFromIntError",
295
+ "PermissionsFromOctalError",
296
+ "PermissionsFromTextError",
297
+ ]