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