PythonIota 1.0.0__py3-none-any.whl → 1.2.0__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.
- pythoniota/__init__.py +10 -2
- pythoniota/_bitflag.py +28 -0
- pythoniota/enum.py +310 -9
- pythoniota/recipes.py +236 -0
- pythoniota/sequence.py +138 -0
- {pythoniota-1.0.0.dist-info → pythoniota-1.2.0.dist-info}/METADATA +1 -1
- pythoniota-1.2.0.dist-info/RECORD +11 -0
- pythoniota-1.0.0.dist-info/RECORD +0 -10
- {pythoniota-1.0.0.dist-info → pythoniota-1.2.0.dist-info}/WHEEL +0 -0
- {pythoniota-1.0.0.dist-info → pythoniota-1.2.0.dist-info}/top_level.txt +0 -0
pythoniota/__init__.py
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
from pythoniota._compat import Iota
|
|
2
2
|
from pythoniota._bitflag import BitFlag
|
|
3
3
|
from pythoniota._safe_eval import safe_eval
|
|
4
|
-
from pythoniota.enum import
|
|
4
|
+
from pythoniota.enum import (
|
|
5
|
+
IotaEnum, IotaBitFlags, IotaStringEnum,
|
|
6
|
+
FlagScope, iota_enum, _EnumMemberProxy,
|
|
7
|
+
)
|
|
5
8
|
from pythoniota.sequence import IotaSequence as iota
|
|
9
|
+
from pythoniota import recipes
|
|
6
10
|
|
|
7
|
-
__version__ = "1.
|
|
11
|
+
__version__ = "1.2.0"
|
|
8
12
|
__all__ = [
|
|
9
13
|
"Iota",
|
|
10
14
|
"IotaEnum",
|
|
11
15
|
"IotaBitFlags",
|
|
16
|
+
"IotaStringEnum",
|
|
12
17
|
"iota",
|
|
13
18
|
"BitFlag",
|
|
19
|
+
"FlagScope",
|
|
20
|
+
"iota_enum",
|
|
14
21
|
"safe_eval",
|
|
22
|
+
"recipes",
|
|
15
23
|
]
|
pythoniota/_bitflag.py
CHANGED
|
@@ -69,6 +69,20 @@ class BitFlag:
|
|
|
69
69
|
def has(self, flag: BitFlag | int) -> bool:
|
|
70
70
|
return flag in self
|
|
71
71
|
|
|
72
|
+
def has_any(self, *flags: BitFlag | int) -> bool:
|
|
73
|
+
for f in flags:
|
|
74
|
+
fv = f._value if isinstance(f, BitFlag) else int(f)
|
|
75
|
+
if self._value & fv:
|
|
76
|
+
return True
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def has_all(self, *flags: BitFlag | int) -> bool:
|
|
80
|
+
for f in flags:
|
|
81
|
+
fv = f._value if isinstance(f, BitFlag) else int(f)
|
|
82
|
+
if (self._value & fv) != fv:
|
|
83
|
+
return False
|
|
84
|
+
return True
|
|
85
|
+
|
|
72
86
|
def decompose(self) -> list[BitFlag]:
|
|
73
87
|
result = []
|
|
74
88
|
v = self._value
|
|
@@ -87,5 +101,19 @@ class BitFlag:
|
|
|
87
101
|
def __str__(self) -> str:
|
|
88
102
|
return self._name or str(self._value)
|
|
89
103
|
|
|
104
|
+
def __format__(self, format_spec: str) -> str:
|
|
105
|
+
if not format_spec:
|
|
106
|
+
return self._name or str(self._value)
|
|
107
|
+
if format_spec == "v":
|
|
108
|
+
return str(self._value)
|
|
109
|
+
if format_spec == "n":
|
|
110
|
+
return self._name
|
|
111
|
+
if format_spec == "b":
|
|
112
|
+
return bin(self._value)
|
|
113
|
+
return format(self._value, format_spec)
|
|
114
|
+
|
|
115
|
+
def __reduce__(self) -> tuple:
|
|
116
|
+
return (BitFlag, (self._value, self._name))
|
|
117
|
+
|
|
90
118
|
def __iter__(self) -> Iterator[BitFlag]:
|
|
91
119
|
return iter(self.decompose())
|
pythoniota/enum.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
import pickle
|
|
3
5
|
from collections import OrderedDict
|
|
4
|
-
from
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from typing import Any, Callable, Iterator
|
|
5
8
|
|
|
6
9
|
from pythoniota._bitflag import BitFlag
|
|
7
10
|
|
|
@@ -124,7 +127,7 @@ class IotaNamespace(dict): # type: ignore[type-arg]
|
|
|
124
127
|
if (
|
|
125
128
|
not key.startswith("_")
|
|
126
129
|
and key != "iota"
|
|
127
|
-
and isinstance(value, (int, float, _IotaCounter))
|
|
130
|
+
and isinstance(value, (int, float, str, _IotaCounter))
|
|
128
131
|
):
|
|
129
132
|
if key not in self._member_names:
|
|
130
133
|
self._member_names.append(key)
|
|
@@ -144,25 +147,54 @@ class IotaEnumMeta(type):
|
|
|
144
147
|
**kwargs: Any,
|
|
145
148
|
) -> IotaEnumMeta:
|
|
146
149
|
member_names = namespace._member_names if isinstance(namespace, IotaNamespace) else []
|
|
147
|
-
members: OrderedDict[str,
|
|
150
|
+
members: OrderedDict[str, Any] = OrderedDict()
|
|
148
151
|
for mname in member_names:
|
|
149
152
|
val = namespace[mname] if mname in namespace else 0
|
|
150
153
|
members[mname] = int(val) if isinstance(val, _IotaCounter) else val
|
|
151
154
|
|
|
152
155
|
cls = super().__new__(mcs, name, bases, dict(namespace))
|
|
153
156
|
cls._members_ = members # type: ignore[attr-defined]
|
|
157
|
+
cls._aliases_: dict[str, str] = {} # type: ignore[attr-defined]
|
|
154
158
|
|
|
155
159
|
is_bitflag = any(
|
|
156
160
|
hasattr(b, "_is_bitflag_base") for b in bases
|
|
157
161
|
)
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
is_string_enum = any(
|
|
163
|
+
hasattr(b, "_is_string_enum_base") for b in bases
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
is_ordered = namespace.get("_ordered_", False) or any(
|
|
167
|
+
getattr(b, "_ordered_", False) for b in bases
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if is_string_enum:
|
|
171
|
+
fmt = namespace.get("_format_", None)
|
|
172
|
+
for mname, mval in list(members.items()):
|
|
173
|
+
if isinstance(mval, _IotaCounter) or isinstance(mval, int):
|
|
174
|
+
if fmt:
|
|
175
|
+
members[mname] = fmt.format(name=mname, index=mval if isinstance(mval, int) else int(mval))
|
|
176
|
+
else:
|
|
177
|
+
members[mname] = mname
|
|
178
|
+
type.__setattr__(cls, mname, members[mname])
|
|
179
|
+
elif is_bitflag:
|
|
180
|
+
for mname, mval in members.items():
|
|
160
181
|
flag = BitFlag(int(mval), mname)
|
|
161
182
|
members[mname] = flag
|
|
162
183
|
type.__setattr__(cls, mname, flag)
|
|
163
|
-
|
|
184
|
+
elif is_ordered:
|
|
185
|
+
for order, (mname, mval) in builtins_enumerate(members.items()):
|
|
186
|
+
proxy = _EnumMemberProxy(mname, mval, cls, order)
|
|
187
|
+
members[mname] = proxy
|
|
188
|
+
type.__setattr__(cls, mname, proxy)
|
|
189
|
+
else:
|
|
190
|
+
for mname, mval in members.items():
|
|
164
191
|
type.__setattr__(cls, mname, mval)
|
|
165
192
|
|
|
193
|
+
doc_lines = [f"{name} enum members:"]
|
|
194
|
+
for mname, mval in members.items():
|
|
195
|
+
doc_lines.append(f" {mname} = {mval}")
|
|
196
|
+
cls.__doc__ = "\n".join(doc_lines)
|
|
197
|
+
|
|
166
198
|
return cls
|
|
167
199
|
|
|
168
200
|
def __iter__(cls) -> Iterator[tuple[str, int | float]]:
|
|
@@ -189,10 +221,148 @@ class IotaEnumMeta(type):
|
|
|
189
221
|
raise AttributeError(f"Cannot delete enum member '{name}'")
|
|
190
222
|
super().__delattr__(name)
|
|
191
223
|
|
|
224
|
+
def __call__(cls, value: Any) -> Any:
|
|
225
|
+
for name, val in cls._members_.items(): # type: ignore[attr-defined]
|
|
226
|
+
cmp_val = int(val) if isinstance(val, BitFlag) else val
|
|
227
|
+
if cmp_val == value:
|
|
228
|
+
return val
|
|
229
|
+
raise ValueError(f"{value!r} is not a valid value in {cls.__name__}")
|
|
230
|
+
|
|
192
231
|
def __repr__(cls) -> str:
|
|
193
232
|
items = ", ".join(f"{k}={v}" for k, v in cls._members_.items()) # type: ignore[attr-defined]
|
|
194
233
|
return f"<{cls.__name__}: {items}>"
|
|
195
234
|
|
|
235
|
+
def __reduce__(cls) -> tuple[Any, ...]:
|
|
236
|
+
return (cls.__class__, (cls.__name__, cls.__bases__, dict(cls._members_))) # type: ignore[attr-defined]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class _EnumMemberProxy:
|
|
240
|
+
"""Wraps an enum member value to support ordered comparison within the same enum."""
|
|
241
|
+
|
|
242
|
+
__slots__ = ("_name", "_value", "_enum_cls", "_order")
|
|
243
|
+
|
|
244
|
+
def __init__(self, name: str, value: Any, enum_cls: type, order: int) -> None:
|
|
245
|
+
object.__setattr__(self, "_name", name)
|
|
246
|
+
object.__setattr__(self, "_value", value)
|
|
247
|
+
object.__setattr__(self, "_enum_cls", enum_cls)
|
|
248
|
+
object.__setattr__(self, "_order", order)
|
|
249
|
+
|
|
250
|
+
def __int__(self) -> int:
|
|
251
|
+
return int(self._value)
|
|
252
|
+
|
|
253
|
+
def __index__(self) -> int:
|
|
254
|
+
return int(self._value)
|
|
255
|
+
|
|
256
|
+
def __eq__(self, other: Any) -> bool:
|
|
257
|
+
if isinstance(other, _EnumMemberProxy):
|
|
258
|
+
return self._value == other._value
|
|
259
|
+
return self._value == other
|
|
260
|
+
|
|
261
|
+
def __ne__(self, other: Any) -> bool:
|
|
262
|
+
return not self.__eq__(other)
|
|
263
|
+
|
|
264
|
+
def __lt__(self, other: Any) -> bool:
|
|
265
|
+
if isinstance(other, _EnumMemberProxy):
|
|
266
|
+
if self._enum_cls is not other._enum_cls:
|
|
267
|
+
raise TypeError(f"'<' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
268
|
+
return self._order < other._order
|
|
269
|
+
return NotImplemented
|
|
270
|
+
|
|
271
|
+
def __le__(self, other: Any) -> bool:
|
|
272
|
+
if isinstance(other, _EnumMemberProxy):
|
|
273
|
+
if self._enum_cls is not other._enum_cls:
|
|
274
|
+
raise TypeError(f"'<=' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
275
|
+
return self._order <= other._order
|
|
276
|
+
return NotImplemented
|
|
277
|
+
|
|
278
|
+
def __gt__(self, other: Any) -> bool:
|
|
279
|
+
if isinstance(other, _EnumMemberProxy):
|
|
280
|
+
if self._enum_cls is not other._enum_cls:
|
|
281
|
+
raise TypeError(f"'>' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
282
|
+
return self._order > other._order
|
|
283
|
+
return NotImplemented
|
|
284
|
+
|
|
285
|
+
def __ge__(self, other: Any) -> bool:
|
|
286
|
+
if isinstance(other, _EnumMemberProxy):
|
|
287
|
+
if self._enum_cls is not other._enum_cls:
|
|
288
|
+
raise TypeError(f"'>=' not supported between members of '{self._enum_cls.__name__}' and '{other._enum_cls.__name__}'")
|
|
289
|
+
return self._order >= other._order
|
|
290
|
+
return NotImplemented
|
|
291
|
+
|
|
292
|
+
def __hash__(self) -> int:
|
|
293
|
+
return hash((self._enum_cls, self._value))
|
|
294
|
+
|
|
295
|
+
def __repr__(self) -> str:
|
|
296
|
+
return f"{self._enum_cls.__name__}.{self._name}"
|
|
297
|
+
|
|
298
|
+
def __str__(self) -> str:
|
|
299
|
+
return self._name
|
|
300
|
+
|
|
301
|
+
def __format__(self, format_spec: str) -> str:
|
|
302
|
+
if not format_spec:
|
|
303
|
+
return self._name
|
|
304
|
+
if format_spec == "v":
|
|
305
|
+
return str(self._value)
|
|
306
|
+
if format_spec == "n":
|
|
307
|
+
return self._name
|
|
308
|
+
if format_spec == "r":
|
|
309
|
+
return repr(self)
|
|
310
|
+
if format_spec == "nv":
|
|
311
|
+
return f"{self._name}={self._value}"
|
|
312
|
+
return format(self._value, format_spec)
|
|
313
|
+
|
|
314
|
+
def __bool__(self) -> bool:
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
def __add__(self, other: Any) -> Any:
|
|
318
|
+
return self._value + (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
319
|
+
|
|
320
|
+
def __radd__(self, other: Any) -> Any:
|
|
321
|
+
return other + self._value
|
|
322
|
+
|
|
323
|
+
def __sub__(self, other: Any) -> Any:
|
|
324
|
+
return self._value - (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
325
|
+
|
|
326
|
+
def __rsub__(self, other: Any) -> Any:
|
|
327
|
+
return other - self._value
|
|
328
|
+
|
|
329
|
+
def __mul__(self, other: Any) -> Any:
|
|
330
|
+
return self._value * (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
331
|
+
|
|
332
|
+
def __rmul__(self, other: Any) -> Any:
|
|
333
|
+
return other * self._value
|
|
334
|
+
|
|
335
|
+
def __or__(self, other: Any) -> Any:
|
|
336
|
+
return self._value | (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
337
|
+
|
|
338
|
+
def __ror__(self, other: Any) -> Any:
|
|
339
|
+
return other | self._value
|
|
340
|
+
|
|
341
|
+
def __and__(self, other: Any) -> Any:
|
|
342
|
+
return self._value & (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
343
|
+
|
|
344
|
+
def __rand__(self, other: Any) -> Any:
|
|
345
|
+
return other & self._value
|
|
346
|
+
|
|
347
|
+
def __lshift__(self, other: Any) -> Any:
|
|
348
|
+
return self._value << (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
349
|
+
|
|
350
|
+
def __rlshift__(self, other: Any) -> Any:
|
|
351
|
+
return other << self._value
|
|
352
|
+
|
|
353
|
+
def __rshift__(self, other: Any) -> Any:
|
|
354
|
+
return self._value >> (other._value if isinstance(other, _EnumMemberProxy) else other)
|
|
355
|
+
|
|
356
|
+
def __rrshift__(self, other: Any) -> Any:
|
|
357
|
+
return other >> self._value
|
|
358
|
+
|
|
359
|
+
def __reduce__(self) -> tuple[Any, ...]:
|
|
360
|
+
return (_restore_member, (self._enum_cls, self._name))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _restore_member(cls: type, name: str) -> Any:
|
|
364
|
+
return getattr(cls, name)
|
|
365
|
+
|
|
196
366
|
|
|
197
367
|
class IotaEnum(metaclass=IotaEnumMeta):
|
|
198
368
|
|
|
@@ -201,20 +371,55 @@ class IotaEnum(metaclass=IotaEnumMeta):
|
|
|
201
371
|
return list(cls._members_.keys()) # type: ignore[attr-defined]
|
|
202
372
|
|
|
203
373
|
@classmethod
|
|
204
|
-
def values(cls) -> list[
|
|
374
|
+
def values(cls) -> list[Any]:
|
|
205
375
|
return list(cls._members_.values()) # type: ignore[attr-defined]
|
|
206
376
|
|
|
207
377
|
@classmethod
|
|
208
|
-
def items(cls) -> list[tuple[str,
|
|
378
|
+
def items(cls) -> list[tuple[str, Any]]:
|
|
209
379
|
return list(cls._members_.items()) # type: ignore[attr-defined]
|
|
210
380
|
|
|
211
381
|
@classmethod
|
|
212
|
-
def from_value(cls, value:
|
|
382
|
+
def from_value(cls, value: Any) -> str | None:
|
|
213
383
|
for name, val in cls._members_.items(): # type: ignore[attr-defined]
|
|
214
384
|
if val == value:
|
|
215
385
|
return name
|
|
386
|
+
for alias, target in cls._aliases_.items(): # type: ignore[attr-defined]
|
|
387
|
+
if cls._members_[target] == value: # type: ignore[attr-defined]
|
|
388
|
+
return alias
|
|
216
389
|
return None
|
|
217
390
|
|
|
391
|
+
@classmethod
|
|
392
|
+
def alias(cls, alias_name: str, target_name: str) -> None:
|
|
393
|
+
if target_name not in cls._members_: # type: ignore[attr-defined]
|
|
394
|
+
raise KeyError(f"'{target_name}' is not a member of {cls.__name__}")
|
|
395
|
+
cls._aliases_[alias_name] = target_name # type: ignore[attr-defined]
|
|
396
|
+
type.__setattr__(cls, alias_name, cls._members_[target_name]) # type: ignore[attr-defined]
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def to_dict(cls) -> dict[str, Any]:
|
|
400
|
+
return dict(cls._members_) # type: ignore[attr-defined]
|
|
401
|
+
|
|
402
|
+
@classmethod
|
|
403
|
+
def to_json(cls, **kwargs: Any) -> str:
|
|
404
|
+
d = {}
|
|
405
|
+
for k, v in cls._members_.items(): # type: ignore[attr-defined]
|
|
406
|
+
d[k] = int(v) if isinstance(v, (BitFlag, float)) and not isinstance(v, str) else v
|
|
407
|
+
return json.dumps(d, **kwargs)
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def from_dict(cls, data: dict[str, Any]) -> dict[str, Any]:
|
|
411
|
+
result = {}
|
|
412
|
+
for name, value in data.items():
|
|
413
|
+
if name in cls._members_: # type: ignore[attr-defined]
|
|
414
|
+
result[name] = cls._members_[name] # type: ignore[attr-defined]
|
|
415
|
+
else:
|
|
416
|
+
raise KeyError(f"'{name}' is not a member of {cls.__name__}")
|
|
417
|
+
return result
|
|
418
|
+
|
|
419
|
+
@classmethod
|
|
420
|
+
def from_json(cls, s: str) -> dict[str, Any]:
|
|
421
|
+
return cls.from_dict(json.loads(s))
|
|
422
|
+
|
|
218
423
|
|
|
219
424
|
class IotaBitFlags(metaclass=IotaEnumMeta):
|
|
220
425
|
_is_bitflag_base = True
|
|
@@ -237,3 +442,99 @@ class IotaBitFlags(metaclass=IotaEnumMeta):
|
|
|
237
442
|
if int(flag) == value:
|
|
238
443
|
return name
|
|
239
444
|
return None
|
|
445
|
+
|
|
446
|
+
@classmethod
|
|
447
|
+
def to_dict(cls) -> dict[str, int]:
|
|
448
|
+
return {k: int(v) for k, v in cls._members_.items()} # type: ignore[attr-defined]
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def to_json(cls, **kwargs: Any) -> str:
|
|
452
|
+
return json.dumps(cls.to_dict(), **kwargs)
|
|
453
|
+
|
|
454
|
+
@classmethod
|
|
455
|
+
def from_dict(cls, data: dict[str, Any]) -> dict[str, BitFlag]:
|
|
456
|
+
result = {}
|
|
457
|
+
for name in data:
|
|
458
|
+
if name in cls._members_: # type: ignore[attr-defined]
|
|
459
|
+
result[name] = cls._members_[name] # type: ignore[attr-defined]
|
|
460
|
+
else:
|
|
461
|
+
raise KeyError(f"'{name}' is not a member of {cls.__name__}")
|
|
462
|
+
return result
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def from_json(cls, s: str) -> dict[str, BitFlag]:
|
|
466
|
+
return cls.from_dict(json.loads(s))
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class IotaStringEnum(IotaEnum):
|
|
470
|
+
_is_string_enum_base = True
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class FlagScope:
|
|
474
|
+
"""Context manager for scoped bitflag operations."""
|
|
475
|
+
|
|
476
|
+
def __init__(self, initial: BitFlag | int = 0) -> None:
|
|
477
|
+
self._flags = initial if isinstance(initial, BitFlag) else BitFlag(initial)
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def flags(self) -> BitFlag:
|
|
481
|
+
return self._flags
|
|
482
|
+
|
|
483
|
+
def grant(self, *flags: BitFlag | int) -> None:
|
|
484
|
+
for f in flags:
|
|
485
|
+
self._flags = self._flags | f
|
|
486
|
+
|
|
487
|
+
def revoke(self, *flags: BitFlag | int) -> None:
|
|
488
|
+
for f in flags:
|
|
489
|
+
fv = int(f)
|
|
490
|
+
self._flags = BitFlag(int(self._flags) & ~fv)
|
|
491
|
+
|
|
492
|
+
def has(self, flag: BitFlag | int) -> bool:
|
|
493
|
+
return self._flags.has(flag)
|
|
494
|
+
|
|
495
|
+
def __enter__(self) -> FlagScope:
|
|
496
|
+
return self
|
|
497
|
+
|
|
498
|
+
def __exit__(self, *args: Any) -> None:
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
def __repr__(self) -> str:
|
|
502
|
+
return f"FlagScope({self._flags})"
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def iota_enum(cls: type | None = None, *, ordered: bool = False) -> Any:
|
|
506
|
+
"""Decorator to convert a plain class into an IotaEnum.
|
|
507
|
+
|
|
508
|
+
Usage:
|
|
509
|
+
@iota_enum
|
|
510
|
+
class Color:
|
|
511
|
+
Red = 0
|
|
512
|
+
Green = 1
|
|
513
|
+
Blue = 2
|
|
514
|
+
|
|
515
|
+
@iota_enum(ordered=True)
|
|
516
|
+
class Priority:
|
|
517
|
+
Low = 0
|
|
518
|
+
Medium = 1
|
|
519
|
+
High = 2
|
|
520
|
+
"""
|
|
521
|
+
def _wrap(klass: type) -> IotaEnumMeta:
|
|
522
|
+
ns = IotaNamespace()
|
|
523
|
+
if ordered:
|
|
524
|
+
ns["_ordered_"] = True
|
|
525
|
+
for key, val in vars(klass).items():
|
|
526
|
+
if key.startswith("_"):
|
|
527
|
+
continue
|
|
528
|
+
if callable(val) and not isinstance(val, (int, float, str)):
|
|
529
|
+
continue
|
|
530
|
+
ns[key] = val
|
|
531
|
+
ns._member_names.append(key)
|
|
532
|
+
return IotaEnumMeta(klass.__name__, (IotaEnum,), ns)
|
|
533
|
+
|
|
534
|
+
if cls is not None:
|
|
535
|
+
return _wrap(cls)
|
|
536
|
+
return _wrap
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
import builtins
|
|
540
|
+
builtins_enumerate = builtins.enumerate
|
pythoniota/recipes.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import math
|
|
5
|
+
from typing import Any, Callable, Iterator
|
|
6
|
+
|
|
7
|
+
from pythoniota.sequence import IotaSequence
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fibonacci(n: int | None = None) -> IotaSequence:
|
|
11
|
+
def _fib_map(_: int) -> int:
|
|
12
|
+
return 0 # placeholder, overridden below
|
|
13
|
+
|
|
14
|
+
class FibSequence(IotaSequence):
|
|
15
|
+
def __iter__(self) -> Iterator[int]:
|
|
16
|
+
a, b = 0, 1
|
|
17
|
+
if self._stop is None:
|
|
18
|
+
while True:
|
|
19
|
+
yield a
|
|
20
|
+
a, b = b, a + b
|
|
21
|
+
else:
|
|
22
|
+
count = 0
|
|
23
|
+
while count < self._stop:
|
|
24
|
+
yield a
|
|
25
|
+
a, b = b, a + b
|
|
26
|
+
count += 1
|
|
27
|
+
|
|
28
|
+
def __repr__(self) -> str:
|
|
29
|
+
if self._stop is None:
|
|
30
|
+
return "fibonacci(infinite)"
|
|
31
|
+
return f"fibonacci({self._stop})"
|
|
32
|
+
|
|
33
|
+
seq = FibSequence.__new__(FibSequence)
|
|
34
|
+
seq._start = 0
|
|
35
|
+
seq._stop = n
|
|
36
|
+
seq._step = 1
|
|
37
|
+
seq._map = None
|
|
38
|
+
return seq
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def geometric(start: int | float, ratio: int | float, n: int | None = None) -> IotaSequence:
|
|
42
|
+
class GeomSequence(IotaSequence):
|
|
43
|
+
def __init__(self, start_val: int | float, ratio_val: int | float, count: int | None) -> None:
|
|
44
|
+
self._start = 0
|
|
45
|
+
self._stop = count
|
|
46
|
+
self._step = 1
|
|
47
|
+
self._map = None
|
|
48
|
+
self._geo_start = start_val
|
|
49
|
+
self._geo_ratio = ratio_val
|
|
50
|
+
|
|
51
|
+
def __iter__(self) -> Iterator[int | float]:
|
|
52
|
+
val = self._geo_start
|
|
53
|
+
if self._stop is None:
|
|
54
|
+
while True:
|
|
55
|
+
yield val
|
|
56
|
+
val *= self._geo_ratio
|
|
57
|
+
else:
|
|
58
|
+
for _ in range(self._stop):
|
|
59
|
+
yield val
|
|
60
|
+
val *= self._geo_ratio
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
if self._stop is None:
|
|
64
|
+
return f"geometric(start={self._geo_start}, ratio={self._geo_ratio}, infinite)"
|
|
65
|
+
return f"geometric(start={self._geo_start}, ratio={self._geo_ratio}, n={self._stop})"
|
|
66
|
+
|
|
67
|
+
return GeomSequence(start, ratio, n)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def primes(n: int | None = None) -> IotaSequence:
|
|
71
|
+
class PrimeSequence(IotaSequence):
|
|
72
|
+
def __iter__(self) -> Iterator[int]:
|
|
73
|
+
count = 0
|
|
74
|
+
candidate = 2
|
|
75
|
+
while n is None or count < n:
|
|
76
|
+
if _is_prime(candidate):
|
|
77
|
+
yield candidate
|
|
78
|
+
count += 1
|
|
79
|
+
candidate += 1
|
|
80
|
+
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
if self._stop is None:
|
|
83
|
+
return "primes(infinite)"
|
|
84
|
+
return f"primes({self._stop})"
|
|
85
|
+
|
|
86
|
+
seq = PrimeSequence.__new__(PrimeSequence)
|
|
87
|
+
seq._start = 0
|
|
88
|
+
seq._stop = n
|
|
89
|
+
seq._step = 1
|
|
90
|
+
seq._map = None
|
|
91
|
+
return seq
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _is_prime(num: int) -> bool:
|
|
95
|
+
if num < 2:
|
|
96
|
+
return False
|
|
97
|
+
if num < 4:
|
|
98
|
+
return True
|
|
99
|
+
if num % 2 == 0 or num % 3 == 0:
|
|
100
|
+
return False
|
|
101
|
+
i = 5
|
|
102
|
+
while i * i <= num:
|
|
103
|
+
if num % i == 0 or num % (i + 2) == 0:
|
|
104
|
+
return False
|
|
105
|
+
i += 6
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def repeat(value: Any, n: int | None = None) -> IotaSequence:
|
|
110
|
+
class RepeatSequence(IotaSequence):
|
|
111
|
+
def __init__(self, val: Any, count: int | None) -> None:
|
|
112
|
+
self._start = 0
|
|
113
|
+
self._stop = count
|
|
114
|
+
self._step = 1
|
|
115
|
+
self._map = None
|
|
116
|
+
self._repeat_val = val
|
|
117
|
+
|
|
118
|
+
def __iter__(self) -> Iterator[Any]:
|
|
119
|
+
if self._stop is None:
|
|
120
|
+
while True:
|
|
121
|
+
yield self._repeat_val
|
|
122
|
+
else:
|
|
123
|
+
for _ in range(self._stop):
|
|
124
|
+
yield self._repeat_val
|
|
125
|
+
|
|
126
|
+
def __repr__(self) -> str:
|
|
127
|
+
if self._stop is None:
|
|
128
|
+
return f"repeat({self._repeat_val!r}, infinite)"
|
|
129
|
+
return f"repeat({self._repeat_val!r}, {self._stop})"
|
|
130
|
+
|
|
131
|
+
return RepeatSequence(value, n)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cycle(iterable: Any, n: int | None = None) -> IotaSequence:
|
|
135
|
+
class CycleSequence(IotaSequence):
|
|
136
|
+
def __init__(self, items: Any, count: int | None) -> None:
|
|
137
|
+
self._start = 0
|
|
138
|
+
self._stop = count
|
|
139
|
+
self._step = 1
|
|
140
|
+
self._map = None
|
|
141
|
+
self._cycle_items = list(items)
|
|
142
|
+
|
|
143
|
+
def __iter__(self) -> Iterator[Any]:
|
|
144
|
+
if not self._cycle_items:
|
|
145
|
+
return
|
|
146
|
+
if self._stop is None:
|
|
147
|
+
while True:
|
|
148
|
+
yield from self._cycle_items
|
|
149
|
+
else:
|
|
150
|
+
emitted = 0
|
|
151
|
+
while emitted < self._stop:
|
|
152
|
+
for item in self._cycle_items:
|
|
153
|
+
if emitted >= self._stop:
|
|
154
|
+
return
|
|
155
|
+
yield item
|
|
156
|
+
emitted += 1
|
|
157
|
+
|
|
158
|
+
def __repr__(self) -> str:
|
|
159
|
+
if self._stop is None:
|
|
160
|
+
return f"cycle({self._cycle_items!r}, infinite)"
|
|
161
|
+
return f"cycle({self._cycle_items!r}, {self._stop})"
|
|
162
|
+
|
|
163
|
+
return CycleSequence(iterable, n)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def collatz(start: int) -> IotaSequence:
|
|
167
|
+
class CollatzSequence(IotaSequence):
|
|
168
|
+
def __init__(self, start_val: int) -> None:
|
|
169
|
+
self._start = 0
|
|
170
|
+
self._stop = None
|
|
171
|
+
self._step = 1
|
|
172
|
+
self._map = None
|
|
173
|
+
self._collatz_start = start_val
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def infinite(self) -> bool:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
def __iter__(self) -> Iterator[int]:
|
|
180
|
+
n = self._collatz_start
|
|
181
|
+
yield n
|
|
182
|
+
while n != 1:
|
|
183
|
+
n = n // 2 if n % 2 == 0 else 3 * n + 1
|
|
184
|
+
yield n
|
|
185
|
+
|
|
186
|
+
def __len__(self) -> int:
|
|
187
|
+
return sum(1 for _ in self)
|
|
188
|
+
|
|
189
|
+
def __repr__(self) -> str:
|
|
190
|
+
return f"collatz({self._collatz_start})"
|
|
191
|
+
|
|
192
|
+
return CollatzSequence(start)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def triangle(n: int | None = None) -> IotaSequence:
|
|
196
|
+
if n is not None:
|
|
197
|
+
return IotaSequence(n, map=lambda i: i * (i + 1) // 2)
|
|
198
|
+
|
|
199
|
+
class TriSequence(IotaSequence):
|
|
200
|
+
def __iter__(self) -> Iterator[int]:
|
|
201
|
+
i = 0
|
|
202
|
+
while True:
|
|
203
|
+
yield i * (i + 1) // 2
|
|
204
|
+
i += 1
|
|
205
|
+
|
|
206
|
+
def __repr__(self) -> str:
|
|
207
|
+
return "triangle(infinite)"
|
|
208
|
+
|
|
209
|
+
seq = TriSequence.__new__(TriSequence)
|
|
210
|
+
seq._start = 0
|
|
211
|
+
seq._stop = None
|
|
212
|
+
seq._step = 1
|
|
213
|
+
seq._map = None
|
|
214
|
+
return seq
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def powers(base: int | float, n: int | None = None) -> IotaSequence:
|
|
218
|
+
if n is not None:
|
|
219
|
+
return IotaSequence(n, map=lambda i: base ** i)
|
|
220
|
+
|
|
221
|
+
class PowerSequence(IotaSequence):
|
|
222
|
+
def __iter__(self) -> Iterator[int | float]:
|
|
223
|
+
i = 0
|
|
224
|
+
while True:
|
|
225
|
+
yield base ** i
|
|
226
|
+
i += 1
|
|
227
|
+
|
|
228
|
+
def __repr__(self) -> str:
|
|
229
|
+
return f"powers({base}, infinite)"
|
|
230
|
+
|
|
231
|
+
seq = PowerSequence.__new__(PowerSequence)
|
|
232
|
+
seq._start = 0
|
|
233
|
+
seq._stop = None
|
|
234
|
+
seq._step = 1
|
|
235
|
+
seq._map = None
|
|
236
|
+
return seq
|
pythoniota/sequence.py
CHANGED
|
@@ -149,6 +149,25 @@ class IotaSequence:
|
|
|
149
149
|
return _reduce(fn, iter(self), initial)
|
|
150
150
|
return _reduce(fn, iter(self))
|
|
151
151
|
|
|
152
|
+
def __add__(self, other: IotaSequence) -> _ConcatSequence:
|
|
153
|
+
return _ConcatSequence(self, other)
|
|
154
|
+
|
|
155
|
+
def __mul__(self, n: int) -> _RepeatSequence:
|
|
156
|
+
return _RepeatSequence(self, n)
|
|
157
|
+
|
|
158
|
+
def __rmul__(self, n: int) -> _RepeatSequence:
|
|
159
|
+
return _RepeatSequence(self, n)
|
|
160
|
+
|
|
161
|
+
def __or__(self, other: IotaSequence) -> _InterleaveSequence:
|
|
162
|
+
return _InterleaveSequence(self, other)
|
|
163
|
+
|
|
164
|
+
def __matmul__(self, other: IotaSequence) -> _ZipMapSequence:
|
|
165
|
+
return _ZipMapSequence(self, other)
|
|
166
|
+
|
|
167
|
+
async def __aiter__(self): # type: ignore[override]
|
|
168
|
+
for item in self:
|
|
169
|
+
yield item
|
|
170
|
+
|
|
152
171
|
def __repr__(self) -> str:
|
|
153
172
|
parts = []
|
|
154
173
|
if self._start != 0:
|
|
@@ -164,6 +183,125 @@ class IotaSequence:
|
|
|
164
183
|
return f"iota({', '.join(parts)})"
|
|
165
184
|
|
|
166
185
|
|
|
186
|
+
class _ConcatSequence(IotaSequence):
|
|
187
|
+
def __init__(self, a: IotaSequence, b: IotaSequence) -> None:
|
|
188
|
+
self._a = a
|
|
189
|
+
self._b = b
|
|
190
|
+
self._start = 0
|
|
191
|
+
self._stop = None if (a.infinite or b.infinite) else (len(a) + len(b))
|
|
192
|
+
self._step = 1
|
|
193
|
+
self._map = None
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def infinite(self) -> bool:
|
|
197
|
+
return self._a.infinite or self._b.infinite
|
|
198
|
+
|
|
199
|
+
def __iter__(self) -> Iterator[Any]:
|
|
200
|
+
yield from self._a
|
|
201
|
+
yield from self._b
|
|
202
|
+
|
|
203
|
+
def __repr__(self) -> str:
|
|
204
|
+
return f"({self._a!r} + {self._b!r})"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class _RepeatSequence(IotaSequence):
|
|
208
|
+
def __init__(self, seq: IotaSequence, n: int) -> None:
|
|
209
|
+
self._seq = seq
|
|
210
|
+
self._n = n
|
|
211
|
+
self._start = 0
|
|
212
|
+
self._stop = None if seq.infinite else len(seq) * n
|
|
213
|
+
self._step = 1
|
|
214
|
+
self._map = None
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def infinite(self) -> bool:
|
|
218
|
+
return self._seq.infinite
|
|
219
|
+
|
|
220
|
+
def __iter__(self) -> Iterator[Any]:
|
|
221
|
+
for _ in range(self._n):
|
|
222
|
+
yield from self._seq
|
|
223
|
+
|
|
224
|
+
def __repr__(self) -> str:
|
|
225
|
+
return f"({self._seq!r} * {self._n})"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class _InterleaveSequence(IotaSequence):
|
|
229
|
+
def __init__(self, a: IotaSequence, b: IotaSequence) -> None:
|
|
230
|
+
self._a = a
|
|
231
|
+
self._b = b
|
|
232
|
+
self._start = 0
|
|
233
|
+
self._step = 1
|
|
234
|
+
self._map = None
|
|
235
|
+
if a.infinite or b.infinite:
|
|
236
|
+
self._stop = None
|
|
237
|
+
else:
|
|
238
|
+
self._stop = len(a) + len(b)
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def infinite(self) -> bool:
|
|
242
|
+
return self._a.infinite or self._b.infinite
|
|
243
|
+
|
|
244
|
+
def __iter__(self) -> Iterator[Any]:
|
|
245
|
+
it_a = iter(self._a)
|
|
246
|
+
it_b = iter(self._b)
|
|
247
|
+
while True:
|
|
248
|
+
exhausted = 0
|
|
249
|
+
try:
|
|
250
|
+
yield next(it_a)
|
|
251
|
+
except StopIteration:
|
|
252
|
+
exhausted += 1
|
|
253
|
+
try:
|
|
254
|
+
yield next(it_b)
|
|
255
|
+
except StopIteration:
|
|
256
|
+
exhausted += 1
|
|
257
|
+
if exhausted == 2:
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
def __len__(self) -> int:
|
|
261
|
+
if self.infinite:
|
|
262
|
+
raise TypeError("infinite sequence has no len()")
|
|
263
|
+
return len(self._a) + len(self._b)
|
|
264
|
+
|
|
265
|
+
def __repr__(self) -> str:
|
|
266
|
+
return f"({self._a!r} | {self._b!r})"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class _ZipMapSequence(IotaSequence):
|
|
270
|
+
def __init__(self, a: IotaSequence, b: IotaSequence) -> None:
|
|
271
|
+
self._a = a
|
|
272
|
+
self._b = b
|
|
273
|
+
self._start = 0
|
|
274
|
+
self._step = 1
|
|
275
|
+
self._map = None
|
|
276
|
+
if a.infinite and b.infinite:
|
|
277
|
+
self._stop = None
|
|
278
|
+
elif a.infinite:
|
|
279
|
+
self._stop = len(b)
|
|
280
|
+
elif b.infinite:
|
|
281
|
+
self._stop = len(a)
|
|
282
|
+
else:
|
|
283
|
+
self._stop = min(len(a), len(b))
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def infinite(self) -> bool:
|
|
287
|
+
return self._a.infinite and self._b.infinite
|
|
288
|
+
|
|
289
|
+
def __iter__(self) -> Iterator[tuple[Any, Any]]:
|
|
290
|
+
yield from builtins_zip(self._a, self._b)
|
|
291
|
+
|
|
292
|
+
def __len__(self) -> int:
|
|
293
|
+
if self.infinite:
|
|
294
|
+
raise TypeError("infinite sequence has no len()")
|
|
295
|
+
if self._a.infinite:
|
|
296
|
+
return len(self._b)
|
|
297
|
+
if self._b.infinite:
|
|
298
|
+
return len(self._a)
|
|
299
|
+
return min(len(self._a), len(self._b))
|
|
300
|
+
|
|
301
|
+
def __repr__(self) -> str:
|
|
302
|
+
return f"({self._a!r} @ {self._b!r})"
|
|
303
|
+
|
|
304
|
+
|
|
167
305
|
import builtins
|
|
168
306
|
|
|
169
307
|
builtins_map = builtins.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pythoniota/__init__.py,sha256=nQiMvhfaPQPcl6UyUmxv3aA39vqle0ARK4BwYMBhwlg,524
|
|
2
|
+
pythoniota/_bitflag.py,sha256=EOOwudYcOECBN665TSFMtWIT9l5bT-hrdN0Gr_x_RQk,3692
|
|
3
|
+
pythoniota/_compat.py,sha256=kscV09rl3-1ccU5hm_dGj7EocKSLj0qUmRVUNOTmBwU,2075
|
|
4
|
+
pythoniota/_safe_eval.py,sha256=FJgZxLp5eBDUg1MchVjkHSqnDOM9Jo6mmiupKZ2CvR4,2051
|
|
5
|
+
pythoniota/enum.py,sha256=Spwp_EosBA_ltMdHn3sRjJ-djF5_C8PJ-uKKbzTfd_4,18102
|
|
6
|
+
pythoniota/recipes.py,sha256=5L4iAiUL2tRi6YG7XUQYTl2cUFY20gMP72BoEbf-Leo,6847
|
|
7
|
+
pythoniota/sequence.py,sha256=Ov76UtMuhiVULdfYwb645eSeGuZZN-qEYhUGNFPiYe4,9559
|
|
8
|
+
pythoniota-1.2.0.dist-info/METADATA,sha256=O87uw7fQS2qThEhLYbcipk1VgGffNoNtPlasV_iLYIw,621
|
|
9
|
+
pythoniota-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
pythoniota-1.2.0.dist-info/top_level.txt,sha256=LoZ35nXIBPHY71-dvmTXyI_g9PBRt8hui5ubG4Huu8c,11
|
|
11
|
+
pythoniota-1.2.0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
pythoniota/__init__.py,sha256=fO2WT2un6fpIg3h5qdB0fck4wP2zg48D3ej1dzyi1_0,353
|
|
2
|
-
pythoniota/_bitflag.py,sha256=9gHyG-yFD-Z5Va9GclBaqKZzELfGx5m5aX5MUtpPQWk,2773
|
|
3
|
-
pythoniota/_compat.py,sha256=kscV09rl3-1ccU5hm_dGj7EocKSLj0qUmRVUNOTmBwU,2075
|
|
4
|
-
pythoniota/_safe_eval.py,sha256=FJgZxLp5eBDUg1MchVjkHSqnDOM9Jo6mmiupKZ2CvR4,2051
|
|
5
|
-
pythoniota/enum.py,sha256=PzIJGNMDDoY1mxEhHIC8b8Nca_0_DJ0X7wV_l-3aLlE,7396
|
|
6
|
-
pythoniota/sequence.py,sha256=7PtxrOcBAOAFtTwwSJwtI_h0vYROZyd3JNKfuF20PSA,5686
|
|
7
|
-
pythoniota-1.0.0.dist-info/METADATA,sha256=ykPzwek9rW-7wL4V5JHMFpHntuPojw6nFGyWL5MXXF4,621
|
|
8
|
-
pythoniota-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
-
pythoniota-1.0.0.dist-info/top_level.txt,sha256=LoZ35nXIBPHY71-dvmTXyI_g9PBRt8hui5ubG4Huu8c,11
|
|
10
|
-
pythoniota-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|