PythonIota 1.0.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 +15 -0
- pythoniota/_bitflag.py +91 -0
- pythoniota/_compat.py +64 -0
- pythoniota/_safe_eval.py +67 -0
- pythoniota/enum.py +239 -0
- pythoniota/sequence.py +172 -0
- pythoniota-1.0.0.dist-info/METADATA +16 -0
- pythoniota-1.0.0.dist-info/RECORD +10 -0
- pythoniota-1.0.0.dist-info/WHEEL +5 -0
- pythoniota-1.0.0.dist-info/top_level.txt +1 -0
pythoniota/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pythoniota._compat import Iota
|
|
2
|
+
from pythoniota._bitflag import BitFlag
|
|
3
|
+
from pythoniota._safe_eval import safe_eval
|
|
4
|
+
from pythoniota.enum import IotaEnum, IotaBitFlags
|
|
5
|
+
from pythoniota.sequence import IotaSequence as iota
|
|
6
|
+
|
|
7
|
+
__version__ = "1.0.0"
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Iota",
|
|
10
|
+
"IotaEnum",
|
|
11
|
+
"IotaBitFlags",
|
|
12
|
+
"iota",
|
|
13
|
+
"BitFlag",
|
|
14
|
+
"safe_eval",
|
|
15
|
+
]
|
pythoniota/_bitflag.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BitFlag:
|
|
7
|
+
__slots__ = ("_value", "_name")
|
|
8
|
+
|
|
9
|
+
def __init__(self, value: int, name: str = "") -> None:
|
|
10
|
+
object.__setattr__(self, "_value", value)
|
|
11
|
+
object.__setattr__(self, "_name", name)
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def value(self) -> int:
|
|
15
|
+
return self._value
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
return self._name
|
|
20
|
+
|
|
21
|
+
def __int__(self) -> int:
|
|
22
|
+
return self._value
|
|
23
|
+
|
|
24
|
+
def __index__(self) -> int:
|
|
25
|
+
return self._value
|
|
26
|
+
|
|
27
|
+
def __bool__(self) -> bool:
|
|
28
|
+
return self._value != 0
|
|
29
|
+
|
|
30
|
+
def __hash__(self) -> int:
|
|
31
|
+
return hash(self._value)
|
|
32
|
+
|
|
33
|
+
def __eq__(self, other: object) -> bool:
|
|
34
|
+
if isinstance(other, BitFlag):
|
|
35
|
+
return self._value == other._value
|
|
36
|
+
if isinstance(other, int):
|
|
37
|
+
return self._value == other
|
|
38
|
+
return NotImplemented
|
|
39
|
+
|
|
40
|
+
def __or__(self, other: BitFlag | int) -> BitFlag:
|
|
41
|
+
other_val = other._value if isinstance(other, BitFlag) else int(other)
|
|
42
|
+
name = f"{self._name}|{other._name if isinstance(other, BitFlag) else other_val}"
|
|
43
|
+
return BitFlag(self._value | other_val, name)
|
|
44
|
+
|
|
45
|
+
def __ror__(self, other: int) -> BitFlag:
|
|
46
|
+
return BitFlag(other | self._value, f"{other}|{self._name}")
|
|
47
|
+
|
|
48
|
+
def __and__(self, other: BitFlag | int) -> BitFlag:
|
|
49
|
+
other_val = other._value if isinstance(other, BitFlag) else int(other)
|
|
50
|
+
return BitFlag(self._value & other_val)
|
|
51
|
+
|
|
52
|
+
def __rand__(self, other: int) -> BitFlag:
|
|
53
|
+
return BitFlag(other & self._value)
|
|
54
|
+
|
|
55
|
+
def __xor__(self, other: BitFlag | int) -> BitFlag:
|
|
56
|
+
other_val = other._value if isinstance(other, BitFlag) else int(other)
|
|
57
|
+
return BitFlag(self._value ^ other_val)
|
|
58
|
+
|
|
59
|
+
def __rxor__(self, other: int) -> BitFlag:
|
|
60
|
+
return BitFlag(other ^ self._value)
|
|
61
|
+
|
|
62
|
+
def __invert__(self) -> BitFlag:
|
|
63
|
+
return BitFlag(~self._value, f"~{self._name}")
|
|
64
|
+
|
|
65
|
+
def __contains__(self, item: BitFlag | int) -> bool:
|
|
66
|
+
item_val = item._value if isinstance(item, BitFlag) else int(item)
|
|
67
|
+
return (self._value & item_val) == item_val and item_val != 0
|
|
68
|
+
|
|
69
|
+
def has(self, flag: BitFlag | int) -> bool:
|
|
70
|
+
return flag in self
|
|
71
|
+
|
|
72
|
+
def decompose(self) -> list[BitFlag]:
|
|
73
|
+
result = []
|
|
74
|
+
v = self._value
|
|
75
|
+
bit = 1
|
|
76
|
+
while bit <= v:
|
|
77
|
+
if v & bit:
|
|
78
|
+
result.append(BitFlag(bit))
|
|
79
|
+
bit <<= 1
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
if self._name:
|
|
84
|
+
return f"BitFlag({self._name}={self._value})"
|
|
85
|
+
return f"BitFlag({self._value})"
|
|
86
|
+
|
|
87
|
+
def __str__(self) -> str:
|
|
88
|
+
return self._name or str(self._value)
|
|
89
|
+
|
|
90
|
+
def __iter__(self) -> Iterator[BitFlag]:
|
|
91
|
+
return iter(self.decompose())
|
pythoniota/_compat.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pythoniota._safe_eval import safe_eval
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Iota:
|
|
10
|
+
"""Legacy Iota class for backward compatibility. Prefer IotaEnum or iota()."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, initial: int = 0) -> None:
|
|
13
|
+
warnings.warn(
|
|
14
|
+
"Iota is deprecated. Use IotaEnum for enumerations or iota() for sequences.",
|
|
15
|
+
DeprecationWarning,
|
|
16
|
+
stacklevel=2,
|
|
17
|
+
)
|
|
18
|
+
self.current: int = initial
|
|
19
|
+
self.values: dict[str, Any] = {}
|
|
20
|
+
|
|
21
|
+
def __call__(self, **kwargs: str) -> None:
|
|
22
|
+
keys = list(kwargs.keys())
|
|
23
|
+
for i, (key, value) in enumerate(kwargs.items()):
|
|
24
|
+
if value == "iota":
|
|
25
|
+
self.values[key] = 0
|
|
26
|
+
self.current = 1
|
|
27
|
+
elif value == "":
|
|
28
|
+
self.values[key] = self.current
|
|
29
|
+
self.current += 1
|
|
30
|
+
elif value == "_":
|
|
31
|
+
self.current += 1
|
|
32
|
+
continue
|
|
33
|
+
elif value.isdigit():
|
|
34
|
+
self.values[key] = int(value)
|
|
35
|
+
self.current = self.values[key] + 1
|
|
36
|
+
else:
|
|
37
|
+
variables = {"iota": self.current}
|
|
38
|
+
variables.update({k: v for k, v in self.values.items() if isinstance(v, (int, float))})
|
|
39
|
+
result = safe_eval(value, variables)
|
|
40
|
+
self.values[key] = result
|
|
41
|
+
self.current = int(result) + 1
|
|
42
|
+
|
|
43
|
+
if key != "_":
|
|
44
|
+
setattr(self, key, self.values[key])
|
|
45
|
+
|
|
46
|
+
def parse_input(self, input_str: str) -> None:
|
|
47
|
+
lines = input_str.strip().split("\n")
|
|
48
|
+
args: dict[str, str] = {}
|
|
49
|
+
for line in lines:
|
|
50
|
+
if "=" in line:
|
|
51
|
+
key, value = line.split("=", 1)
|
|
52
|
+
key = key.strip()
|
|
53
|
+
value = value.strip()
|
|
54
|
+
else:
|
|
55
|
+
key = line.strip()
|
|
56
|
+
value = ""
|
|
57
|
+
args[key] = value
|
|
58
|
+
self(**args)
|
|
59
|
+
|
|
60
|
+
# Backward-compatible alias
|
|
61
|
+
__parseInput__ = parse_input
|
|
62
|
+
|
|
63
|
+
def get_values(self) -> dict[str, Any]:
|
|
64
|
+
return self.values
|
pythoniota/_safe_eval.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import operator
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
_BIN_OPS: dict[type, Any] = {
|
|
8
|
+
ast.Add: operator.add,
|
|
9
|
+
ast.Sub: operator.sub,
|
|
10
|
+
ast.Mult: operator.mul,
|
|
11
|
+
ast.Div: operator.truediv,
|
|
12
|
+
ast.FloorDiv: operator.floordiv,
|
|
13
|
+
ast.Mod: operator.mod,
|
|
14
|
+
ast.Pow: operator.pow,
|
|
15
|
+
ast.LShift: operator.lshift,
|
|
16
|
+
ast.RShift: operator.rshift,
|
|
17
|
+
ast.BitOr: operator.or_,
|
|
18
|
+
ast.BitAnd: operator.and_,
|
|
19
|
+
ast.BitXor: operator.xor,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_UNARY_OPS: dict[type, Any] = {
|
|
23
|
+
ast.USub: operator.neg,
|
|
24
|
+
ast.UAdd: operator.pos,
|
|
25
|
+
ast.Invert: operator.invert,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _eval_node(node: ast.expr, variables: dict[str, int | float]) -> int | float:
|
|
30
|
+
if isinstance(node, ast.Constant):
|
|
31
|
+
if not isinstance(node.value, (int, float)):
|
|
32
|
+
raise ValueError(f"Unsupported constant type: {type(node.value).__name__}")
|
|
33
|
+
return node.value
|
|
34
|
+
|
|
35
|
+
if isinstance(node, ast.Name):
|
|
36
|
+
if node.id not in variables:
|
|
37
|
+
raise ValueError(f"Undefined variable: {node.id}")
|
|
38
|
+
return variables[node.id]
|
|
39
|
+
|
|
40
|
+
if isinstance(node, ast.BinOp):
|
|
41
|
+
op_fn = _BIN_OPS.get(type(node.op))
|
|
42
|
+
if op_fn is None:
|
|
43
|
+
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
|
44
|
+
return op_fn(_eval_node(node.left, variables), _eval_node(node.right, variables))
|
|
45
|
+
|
|
46
|
+
if isinstance(node, ast.UnaryOp):
|
|
47
|
+
op_fn = _UNARY_OPS.get(type(node.op))
|
|
48
|
+
if op_fn is None:
|
|
49
|
+
raise ValueError(f"Unsupported unary operator: {type(node.op).__name__}")
|
|
50
|
+
return op_fn(_eval_node(node.operand, variables))
|
|
51
|
+
|
|
52
|
+
raise ValueError(f"Unsupported node: {type(node).__name__}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def safe_eval(expr: str, variables: dict[str, int | float] | None = None) -> int | float:
|
|
56
|
+
if variables is None:
|
|
57
|
+
variables = {}
|
|
58
|
+
tree = ast.parse(expr.strip(), mode="eval")
|
|
59
|
+
return _eval_node(tree.body, variables)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_safe_expression(expr: str) -> bool:
|
|
63
|
+
try:
|
|
64
|
+
safe_eval(expr)
|
|
65
|
+
return True
|
|
66
|
+
except (ValueError, SyntaxError):
|
|
67
|
+
return False
|
pythoniota/enum.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from typing import Any, Iterator
|
|
5
|
+
|
|
6
|
+
from pythoniota._bitflag import BitFlag
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _IotaCounter:
|
|
10
|
+
"""Sentinel returned by IotaNamespace when 'iota' is read. Holds the current counter value."""
|
|
11
|
+
|
|
12
|
+
__slots__ = ("value",)
|
|
13
|
+
|
|
14
|
+
def __init__(self, value: int) -> None:
|
|
15
|
+
self.value = value
|
|
16
|
+
|
|
17
|
+
def __int__(self) -> int:
|
|
18
|
+
return self.value
|
|
19
|
+
|
|
20
|
+
def __index__(self) -> int:
|
|
21
|
+
return self.value
|
|
22
|
+
|
|
23
|
+
def __lshift__(self, other: Any) -> int:
|
|
24
|
+
return self.value << int(other)
|
|
25
|
+
|
|
26
|
+
def __rlshift__(self, other: Any) -> int:
|
|
27
|
+
return int(other) << self.value
|
|
28
|
+
|
|
29
|
+
def __rshift__(self, other: Any) -> int:
|
|
30
|
+
return self.value >> int(other)
|
|
31
|
+
|
|
32
|
+
def __rrshift__(self, other: Any) -> int:
|
|
33
|
+
return int(other) >> self.value
|
|
34
|
+
|
|
35
|
+
def __add__(self, other: Any) -> int:
|
|
36
|
+
return self.value + int(other)
|
|
37
|
+
|
|
38
|
+
def __radd__(self, other: Any) -> int:
|
|
39
|
+
return int(other) + self.value
|
|
40
|
+
|
|
41
|
+
def __sub__(self, other: Any) -> int:
|
|
42
|
+
return self.value - int(other)
|
|
43
|
+
|
|
44
|
+
def __rsub__(self, other: Any) -> int:
|
|
45
|
+
return int(other) - self.value
|
|
46
|
+
|
|
47
|
+
def __mul__(self, other: Any) -> int:
|
|
48
|
+
return self.value * int(other)
|
|
49
|
+
|
|
50
|
+
def __rmul__(self, other: Any) -> int:
|
|
51
|
+
return int(other) * self.value
|
|
52
|
+
|
|
53
|
+
def __floordiv__(self, other: Any) -> int:
|
|
54
|
+
return self.value // int(other)
|
|
55
|
+
|
|
56
|
+
def __rfloordiv__(self, other: Any) -> int:
|
|
57
|
+
return int(other) // self.value
|
|
58
|
+
|
|
59
|
+
def __mod__(self, other: Any) -> int:
|
|
60
|
+
return self.value % int(other)
|
|
61
|
+
|
|
62
|
+
def __rmod__(self, other: Any) -> int:
|
|
63
|
+
return int(other) % self.value
|
|
64
|
+
|
|
65
|
+
def __pow__(self, other: Any) -> int:
|
|
66
|
+
return self.value ** int(other)
|
|
67
|
+
|
|
68
|
+
def __rpow__(self, other: Any) -> int:
|
|
69
|
+
return int(other) ** self.value
|
|
70
|
+
|
|
71
|
+
def __or__(self, other: Any) -> int:
|
|
72
|
+
return self.value | int(other)
|
|
73
|
+
|
|
74
|
+
def __ror__(self, other: Any) -> int:
|
|
75
|
+
return int(other) | self.value
|
|
76
|
+
|
|
77
|
+
def __and__(self, other: Any) -> int:
|
|
78
|
+
return self.value & int(other)
|
|
79
|
+
|
|
80
|
+
def __rand__(self, other: Any) -> int:
|
|
81
|
+
return int(other) & self.value
|
|
82
|
+
|
|
83
|
+
def __xor__(self, other: Any) -> int:
|
|
84
|
+
return self.value ^ int(other)
|
|
85
|
+
|
|
86
|
+
def __rxor__(self, other: Any) -> int:
|
|
87
|
+
return int(other) ^ self.value
|
|
88
|
+
|
|
89
|
+
def __neg__(self) -> int:
|
|
90
|
+
return -self.value
|
|
91
|
+
|
|
92
|
+
def __pos__(self) -> int:
|
|
93
|
+
return +self.value
|
|
94
|
+
|
|
95
|
+
def __invert__(self) -> int:
|
|
96
|
+
return ~self.value
|
|
97
|
+
|
|
98
|
+
def __eq__(self, other: Any) -> bool:
|
|
99
|
+
return self.value == int(other) if isinstance(other, (int, _IotaCounter)) else NotImplemented
|
|
100
|
+
|
|
101
|
+
def __hash__(self) -> int:
|
|
102
|
+
return hash(self.value)
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
return f"iota({self.value})"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class IotaNamespace(dict): # type: ignore[type-arg]
|
|
109
|
+
|
|
110
|
+
def __init__(self) -> None:
|
|
111
|
+
super().__init__()
|
|
112
|
+
self._counter = 0
|
|
113
|
+
self._member_names: list[str] = []
|
|
114
|
+
|
|
115
|
+
def __getitem__(self, key: str) -> Any:
|
|
116
|
+
if key == "iota":
|
|
117
|
+
val = _IotaCounter(self._counter)
|
|
118
|
+
self._counter += 1
|
|
119
|
+
return val
|
|
120
|
+
return super().__getitem__(key)
|
|
121
|
+
|
|
122
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
123
|
+
super().__setitem__(key, value)
|
|
124
|
+
if (
|
|
125
|
+
not key.startswith("_")
|
|
126
|
+
and key != "iota"
|
|
127
|
+
and isinstance(value, (int, float, _IotaCounter))
|
|
128
|
+
):
|
|
129
|
+
if key not in self._member_names:
|
|
130
|
+
self._member_names.append(key)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class IotaEnumMeta(type):
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def __prepare__(mcs, name: str, bases: tuple[type, ...], **kwargs: Any) -> IotaNamespace: # type: ignore[override]
|
|
137
|
+
return IotaNamespace()
|
|
138
|
+
|
|
139
|
+
def __new__(
|
|
140
|
+
mcs,
|
|
141
|
+
name: str,
|
|
142
|
+
bases: tuple[type, ...],
|
|
143
|
+
namespace: IotaNamespace, # type: ignore[override]
|
|
144
|
+
**kwargs: Any,
|
|
145
|
+
) -> IotaEnumMeta:
|
|
146
|
+
member_names = namespace._member_names if isinstance(namespace, IotaNamespace) else []
|
|
147
|
+
members: OrderedDict[str, int | float] = OrderedDict()
|
|
148
|
+
for mname in member_names:
|
|
149
|
+
val = namespace[mname] if mname in namespace else 0
|
|
150
|
+
members[mname] = int(val) if isinstance(val, _IotaCounter) else val
|
|
151
|
+
|
|
152
|
+
cls = super().__new__(mcs, name, bases, dict(namespace))
|
|
153
|
+
cls._members_ = members # type: ignore[attr-defined]
|
|
154
|
+
|
|
155
|
+
is_bitflag = any(
|
|
156
|
+
hasattr(b, "_is_bitflag_base") for b in bases
|
|
157
|
+
)
|
|
158
|
+
for mname, mval in members.items():
|
|
159
|
+
if is_bitflag:
|
|
160
|
+
flag = BitFlag(int(mval), mname)
|
|
161
|
+
members[mname] = flag
|
|
162
|
+
type.__setattr__(cls, mname, flag)
|
|
163
|
+
else:
|
|
164
|
+
type.__setattr__(cls, mname, mval)
|
|
165
|
+
|
|
166
|
+
return cls
|
|
167
|
+
|
|
168
|
+
def __iter__(cls) -> Iterator[tuple[str, int | float]]:
|
|
169
|
+
return iter(cls._members_.items()) # type: ignore[attr-defined]
|
|
170
|
+
|
|
171
|
+
def __contains__(cls, item: Any) -> bool:
|
|
172
|
+
if isinstance(item, str):
|
|
173
|
+
return item in cls._members_ # type: ignore[attr-defined]
|
|
174
|
+
return item in cls._members_.values() # type: ignore[attr-defined]
|
|
175
|
+
|
|
176
|
+
def __len__(cls) -> int:
|
|
177
|
+
return len(cls._members_) # type: ignore[attr-defined]
|
|
178
|
+
|
|
179
|
+
def __getitem__(cls, key: str) -> int | float:
|
|
180
|
+
return cls._members_[key] # type: ignore[attr-defined]
|
|
181
|
+
|
|
182
|
+
def __setattr__(cls, name: str, value: Any) -> None:
|
|
183
|
+
if hasattr(cls, "_members_") and name in cls._members_: # type: ignore[attr-defined]
|
|
184
|
+
raise AttributeError(f"Cannot modify enum member '{name}'")
|
|
185
|
+
super().__setattr__(name, value)
|
|
186
|
+
|
|
187
|
+
def __delattr__(cls, name: str) -> None:
|
|
188
|
+
if hasattr(cls, "_members_") and name in cls._members_: # type: ignore[attr-defined]
|
|
189
|
+
raise AttributeError(f"Cannot delete enum member '{name}'")
|
|
190
|
+
super().__delattr__(name)
|
|
191
|
+
|
|
192
|
+
def __repr__(cls) -> str:
|
|
193
|
+
items = ", ".join(f"{k}={v}" for k, v in cls._members_.items()) # type: ignore[attr-defined]
|
|
194
|
+
return f"<{cls.__name__}: {items}>"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class IotaEnum(metaclass=IotaEnumMeta):
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def names(cls) -> list[str]:
|
|
201
|
+
return list(cls._members_.keys()) # type: ignore[attr-defined]
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def values(cls) -> list[int | float]:
|
|
205
|
+
return list(cls._members_.values()) # type: ignore[attr-defined]
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def items(cls) -> list[tuple[str, int | float]]:
|
|
209
|
+
return list(cls._members_.items()) # type: ignore[attr-defined]
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def from_value(cls, value: int | float) -> str | None:
|
|
213
|
+
for name, val in cls._members_.items(): # type: ignore[attr-defined]
|
|
214
|
+
if val == value:
|
|
215
|
+
return name
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class IotaBitFlags(metaclass=IotaEnumMeta):
|
|
220
|
+
_is_bitflag_base = True
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def names(cls) -> list[str]:
|
|
224
|
+
return list(cls._members_.keys()) # type: ignore[attr-defined]
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def values(cls) -> list[BitFlag]:
|
|
228
|
+
return list(cls._members_.values()) # type: ignore[attr-defined]
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def items(cls) -> list[tuple[str, BitFlag]]:
|
|
232
|
+
return list(cls._members_.items()) # type: ignore[attr-defined]
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def from_value(cls, value: int) -> str | None:
|
|
236
|
+
for name, flag in cls._members_.items(): # type: ignore[attr-defined]
|
|
237
|
+
if int(flag) == value:
|
|
238
|
+
return name
|
|
239
|
+
return None
|
pythoniota/sequence.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import math
|
|
5
|
+
from collections import deque
|
|
6
|
+
from functools import reduce as _reduce
|
|
7
|
+
from typing import Any, Callable, Iterator, TypeVar, overload
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IotaSequence:
|
|
13
|
+
|
|
14
|
+
@overload
|
|
15
|
+
def __init__(self) -> None: ...
|
|
16
|
+
@overload
|
|
17
|
+
def __init__(self, stop: int, /, *, map: Callable[[int], Any] | None = None) -> None: ...
|
|
18
|
+
@overload
|
|
19
|
+
def __init__(self, start: int, stop: int, step: int = 1, *, map: Callable[[int], Any] | None = None) -> None: ...
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args: int, map: Callable[[int], Any] | None = None, step: int | None = None) -> None:
|
|
22
|
+
if len(args) == 0:
|
|
23
|
+
self._start = 0
|
|
24
|
+
self._stop: int | None = None
|
|
25
|
+
self._step = step if step is not None else 1
|
|
26
|
+
elif len(args) == 1:
|
|
27
|
+
self._start = 0
|
|
28
|
+
self._stop = args[0]
|
|
29
|
+
self._step = step if step is not None else 1
|
|
30
|
+
elif len(args) == 2:
|
|
31
|
+
self._start = args[0]
|
|
32
|
+
self._stop = args[1]
|
|
33
|
+
self._step = step if step is not None else 1
|
|
34
|
+
elif len(args) == 3:
|
|
35
|
+
self._start = args[0]
|
|
36
|
+
self._stop = args[1]
|
|
37
|
+
self._step = args[2]
|
|
38
|
+
else:
|
|
39
|
+
raise TypeError(f"iota expected at most 3 positional arguments, got {len(args)}")
|
|
40
|
+
|
|
41
|
+
if self._step == 0:
|
|
42
|
+
raise ValueError("step must not be zero")
|
|
43
|
+
|
|
44
|
+
self._map = map
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def infinite(self) -> bool:
|
|
48
|
+
return self._stop is None
|
|
49
|
+
|
|
50
|
+
def _raw_iter(self) -> Iterator[int]:
|
|
51
|
+
i = self._start
|
|
52
|
+
if self._stop is None:
|
|
53
|
+
while True:
|
|
54
|
+
yield i
|
|
55
|
+
i += self._step
|
|
56
|
+
else:
|
|
57
|
+
if self._step > 0:
|
|
58
|
+
while i < self._stop:
|
|
59
|
+
yield i
|
|
60
|
+
i += self._step
|
|
61
|
+
else:
|
|
62
|
+
while i > self._stop:
|
|
63
|
+
yield i
|
|
64
|
+
i += self._step
|
|
65
|
+
|
|
66
|
+
def __iter__(self) -> Iterator[Any]:
|
|
67
|
+
it = self._raw_iter()
|
|
68
|
+
if self._map is not None:
|
|
69
|
+
return builtins_map(self._map, it)
|
|
70
|
+
return it
|
|
71
|
+
|
|
72
|
+
def __len__(self) -> int:
|
|
73
|
+
if self.infinite:
|
|
74
|
+
raise TypeError("infinite sequence has no len()")
|
|
75
|
+
assert self._stop is not None
|
|
76
|
+
n = math.ceil((self._stop - self._start) / self._step)
|
|
77
|
+
return max(0, n)
|
|
78
|
+
|
|
79
|
+
def __getitem__(self, index: int | slice) -> Any:
|
|
80
|
+
if isinstance(index, slice):
|
|
81
|
+
start, stop, step = index.indices(len(self) if not self.infinite else 2**31)
|
|
82
|
+
return list(itertools.islice(iter(self), start, stop, step))
|
|
83
|
+
if index < 0:
|
|
84
|
+
if self.infinite:
|
|
85
|
+
raise IndexError("negative indexing not supported on infinite sequences")
|
|
86
|
+
index = len(self) + index
|
|
87
|
+
return next(itertools.islice(iter(self), index, index + 1))
|
|
88
|
+
|
|
89
|
+
def __contains__(self, value: Any) -> bool:
|
|
90
|
+
if self.infinite:
|
|
91
|
+
raise TypeError("cannot check membership on infinite sequence")
|
|
92
|
+
return value in iter(self)
|
|
93
|
+
|
|
94
|
+
def __reversed__(self) -> Iterator[Any]:
|
|
95
|
+
if self.infinite:
|
|
96
|
+
raise TypeError("cannot reverse infinite sequence")
|
|
97
|
+
return reversed(list(self))
|
|
98
|
+
|
|
99
|
+
def take(self, n: int) -> list[Any]:
|
|
100
|
+
return list(itertools.islice(iter(self), n))
|
|
101
|
+
|
|
102
|
+
def map(self, fn: Callable[[Any], Any]) -> IotaSequence:
|
|
103
|
+
outer_map = self._map
|
|
104
|
+
if outer_map is not None:
|
|
105
|
+
combined: Callable[[int], Any] = lambda x, _om=outer_map, _fn=fn: _fn(_om(x))
|
|
106
|
+
else:
|
|
107
|
+
combined = fn
|
|
108
|
+
seq = IotaSequence.__new__(IotaSequence)
|
|
109
|
+
seq._start = self._start
|
|
110
|
+
seq._stop = self._stop
|
|
111
|
+
seq._step = self._step
|
|
112
|
+
seq._map = combined
|
|
113
|
+
return seq
|
|
114
|
+
|
|
115
|
+
def filter(self, fn: Callable[[Any], bool]) -> Iterator[Any]:
|
|
116
|
+
return builtins_filter(fn, iter(self))
|
|
117
|
+
|
|
118
|
+
def accumulate(self, fn: Callable[[Any, Any], Any] | None = None) -> Iterator[Any]:
|
|
119
|
+
return itertools.accumulate(iter(self), fn)
|
|
120
|
+
|
|
121
|
+
def enumerate(self, start: int = 0) -> Iterator[tuple[int, Any]]:
|
|
122
|
+
return builtins_enumerate(iter(self), start)
|
|
123
|
+
|
|
124
|
+
def zip(self, *others: Any) -> Iterator[tuple[Any, ...]]:
|
|
125
|
+
return builtins_zip(iter(self), *others)
|
|
126
|
+
|
|
127
|
+
def chunk(self, n: int) -> Iterator[list[Any]]:
|
|
128
|
+
it = iter(self)
|
|
129
|
+
while True:
|
|
130
|
+
batch = list(itertools.islice(it, n))
|
|
131
|
+
if not batch:
|
|
132
|
+
break
|
|
133
|
+
yield batch
|
|
134
|
+
|
|
135
|
+
def window(self, n: int) -> Iterator[tuple[Any, ...]]:
|
|
136
|
+
it = iter(self)
|
|
137
|
+
win: deque[Any] = deque(itertools.islice(it, n), maxlen=n)
|
|
138
|
+
if len(win) < n:
|
|
139
|
+
return
|
|
140
|
+
yield tuple(win)
|
|
141
|
+
for item in it:
|
|
142
|
+
win.append(item)
|
|
143
|
+
yield tuple(win)
|
|
144
|
+
|
|
145
|
+
def reduce(self, fn: Callable[[Any, Any], Any], initial: Any = None) -> Any:
|
|
146
|
+
if self.infinite:
|
|
147
|
+
raise TypeError("cannot reduce infinite sequence")
|
|
148
|
+
if initial is not None:
|
|
149
|
+
return _reduce(fn, iter(self), initial)
|
|
150
|
+
return _reduce(fn, iter(self))
|
|
151
|
+
|
|
152
|
+
def __repr__(self) -> str:
|
|
153
|
+
parts = []
|
|
154
|
+
if self._start != 0:
|
|
155
|
+
parts.append(f"start={self._start}")
|
|
156
|
+
if self._stop is not None:
|
|
157
|
+
parts.append(f"stop={self._stop}")
|
|
158
|
+
else:
|
|
159
|
+
parts.append("infinite")
|
|
160
|
+
if self._step != 1:
|
|
161
|
+
parts.append(f"step={self._step}")
|
|
162
|
+
if self._map is not None:
|
|
163
|
+
parts.append("mapped")
|
|
164
|
+
return f"iota({', '.join(parts)})"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
import builtins
|
|
168
|
+
|
|
169
|
+
builtins_map = builtins.map
|
|
170
|
+
builtins_filter = builtins.filter
|
|
171
|
+
builtins_enumerate = builtins.enumerate
|
|
172
|
+
builtins_zip = builtins.zip
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PythonIota
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Go-style iota enumerations and flexible sequence generators for Python
|
|
5
|
+
Author: Equinox
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,10 @@
|
|
|
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,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pythoniota
|