dycw-utilities 0.174.8__py3-none-any.whl → 0.174.10__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.10
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=0CWy0Ep-3LcvfPyRs5NDWEzJToyo7mozx2oLa1j9OoA,61
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=HSVpLef4lfG6GsbeDgYbKRe_92Kk6dNPrB0kw7GsXMY,8093
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.10.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
102
+ dycw_utilities-0.174.10.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
103
+ dycw_utilities-0.174.10.dist-info/METADATA,sha256=xJJPzLScczDipOwF6AP4Tqr8p__BH8NV6WcuwhqZIq0,1710
104
+ dycw_utilities-0.174.10.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.10"
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,264 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from functools import reduce
5
+ from operator import or_
6
+ from stat import (
7
+ S_IRGRP,
8
+ S_IROTH,
9
+ S_IRUSR,
10
+ S_IWGRP,
11
+ S_IWOTH,
12
+ S_IWUSR,
13
+ S_IXGRP,
14
+ S_IXOTH,
15
+ S_IXUSR,
16
+ )
17
+ from typing import Literal, Self, override
18
+
19
+ from utilities.dataclasses import replace_non_sentinel
20
+ from utilities.re import ExtractGroupsError, extract_groups
21
+ from utilities.sentinel import Sentinel, sentinel
22
+
23
+
24
+ @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
25
+ class Permissions:
26
+ user_read: bool = False
27
+ user_write: bool = False
28
+ user_execute: bool = False
29
+ group_read: bool = False
30
+ group_write: bool = False
31
+ group_execute: bool = False
32
+ others_read: bool = False
33
+ others_write: bool = False
34
+ others_execute: bool = False
35
+
36
+ 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)
57
+
58
+ @override
59
+ def __repr__(self) -> str:
60
+ return ",".join([
61
+ self._repr_parts(
62
+ "u",
63
+ read=self.user_read,
64
+ write=self.user_write,
65
+ execute=self.user_execute,
66
+ ),
67
+ self._repr_parts(
68
+ "g",
69
+ read=self.group_read,
70
+ write=self.group_write,
71
+ execute=self.group_execute,
72
+ ),
73
+ self._repr_parts(
74
+ "o",
75
+ read=self.others_read,
76
+ write=self.others_write,
77
+ execute=self.others_execute,
78
+ ),
79
+ ])
80
+
81
+ def _repr_parts(
82
+ self,
83
+ prefix: Literal["u", "g", "o"],
84
+ /,
85
+ *,
86
+ read: bool = False,
87
+ write: bool = False,
88
+ execute: bool = False,
89
+ ) -> 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")
97
+ return f"{prefix}={''.join(parts)}"
98
+
99
+ @override
100
+ def __str__(self) -> str:
101
+ return repr(self)
102
+
103
+ @classmethod
104
+ def from_int(cls, n: int, /) -> Self:
105
+ 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)
110
+ return cls(
111
+ user_read=user_read,
112
+ user_write=user_write,
113
+ user_execute=user_execute,
114
+ group_read=group_read,
115
+ group_write=group_write,
116
+ group_execute=group_execute,
117
+ others_read=others_read,
118
+ others_write=others_write,
119
+ others_execute=others_execute,
120
+ )
121
+
122
+ @classmethod
123
+ def _from_int(cls, n: int, digit: int, /) -> tuple[bool, bool, bool]:
124
+ if not (0 <= digit <= 7):
125
+ raise PermissionsFromIntDigitError(n=n, digit=digit)
126
+ return bool(4 & digit), bool(2 & digit), bool(1 & digit)
127
+
128
+ @classmethod
129
+ def from_octal(cls, n: int, /) -> Self:
130
+ if 0o0 <= n <= 0o777:
131
+ return cls(
132
+ user_read=bool(n & S_IRUSR),
133
+ user_write=bool(n & S_IWUSR),
134
+ user_execute=bool(n & S_IXUSR),
135
+ group_read=bool(n & S_IRGRP),
136
+ group_write=bool(n & S_IWGRP),
137
+ group_execute=bool(n & S_IXGRP),
138
+ others_read=bool(n & S_IROTH),
139
+ others_write=bool(n & S_IWOTH),
140
+ others_execute=bool(n & S_IXOTH),
141
+ )
142
+ raise PermissionsFromOctalError(n=n)
143
+
144
+ @classmethod
145
+ def from_text(cls, text: str, /) -> Self:
146
+ try:
147
+ user, group, others = extract_groups(
148
+ r"^u=(r?w?x?),g=(r?w?x?),o=(r?w?x?)$", text
149
+ )
150
+ except ExtractGroupsError:
151
+ raise PermissionsFromTextError(text=text) from None
152
+ user_read, user_write, user_execute = cls._from_text_part(user)
153
+ group_read, group_write, group_execute = cls._from_text_part(group)
154
+ others_read, others_write, others_execute = cls._from_text_part(others)
155
+ return cls(
156
+ user_read=user_read,
157
+ user_write=user_write,
158
+ user_execute=user_execute,
159
+ group_read=group_read,
160
+ group_write=group_write,
161
+ group_execute=group_execute,
162
+ others_read=others_read,
163
+ others_write=others_write,
164
+ others_execute=others_execute,
165
+ )
166
+
167
+ @classmethod
168
+ def _from_text_part(cls, text: str, /) -> tuple[bool, bool, bool]:
169
+ read, write, execute = extract_groups("^(r?)(w?)(x?)$", text)
170
+ return read != "", write != "", execute != ""
171
+
172
+ @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)
186
+
187
+ def replace(
188
+ self,
189
+ *,
190
+ user_read: bool | Sentinel = sentinel,
191
+ user_write: bool | Sentinel = sentinel,
192
+ user_execute: bool | Sentinel = sentinel,
193
+ group_read: bool | Sentinel = sentinel,
194
+ group_write: bool | Sentinel = sentinel,
195
+ group_execute: bool | Sentinel = sentinel,
196
+ others_read: bool | Sentinel = sentinel,
197
+ others_write: bool | Sentinel = sentinel,
198
+ others_execute: bool | Sentinel = sentinel,
199
+ ) -> Self:
200
+ return replace_non_sentinel(
201
+ self,
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
+
214
+ @dataclass(kw_only=True, slots=True)
215
+ class PermissionsError(Exception): ...
216
+
217
+
218
+ @dataclass(kw_only=True, slots=True)
219
+ class PermissionsFromIntError(PermissionsError):
220
+ n: int
221
+
222
+
223
+ @dataclass(kw_only=True, slots=True)
224
+ class PermissionsFromIntRangeError(PermissionsFromIntError):
225
+ @override
226
+ def __str__(self) -> str:
227
+ return f"Invalid integer for permissions; got {self.n}"
228
+
229
+
230
+ @dataclass(kw_only=True, slots=True)
231
+ class PermissionsFromIntDigitError(PermissionsFromIntError):
232
+ digit: int
233
+
234
+ @override
235
+ def __str__(self) -> str:
236
+ return f"Invalid integer for permissions; got digit {self.digit} in {self.n}"
237
+
238
+
239
+ @dataclass(kw_only=True, slots=True)
240
+ class PermissionsFromOctalError(PermissionsError):
241
+ n: int
242
+
243
+ @override
244
+ def __str__(self) -> str:
245
+ return f"Invalid octal for permissions; got {oct(self.n)}"
246
+
247
+
248
+ @dataclass(kw_only=True, slots=True)
249
+ class PermissionsFromTextError(PermissionsError):
250
+ text: str
251
+
252
+ @override
253
+ def __str__(self) -> str:
254
+ return f"Invalid string for permissions; got {self.text!r}"
255
+
256
+
257
+ __all__ = [
258
+ "Permissions",
259
+ "PermissionsError",
260
+ "PermissionsFromIntDigitError",
261
+ "PermissionsFromIntError",
262
+ "PermissionsFromOctalError",
263
+ "PermissionsFromTextError",
264
+ ]