PythonIota 0.0.2__tar.gz → 1.0.0__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.
@@ -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,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "PythonIota"
7
+ version = "1.0.0"
8
+ description = "Go-style iota enumerations and flexible sequence generators for Python"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [{name = "Equinox"}]
12
+ requires-python = ">=3.10"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Typing :: Typed",
22
+ ]
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
@@ -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,17 @@
1
+ pyproject.toml
2
+ src/PythonIota.egg-info/PKG-INFO
3
+ src/PythonIota.egg-info/SOURCES.txt
4
+ src/PythonIota.egg-info/dependency_links.txt
5
+ src/PythonIota.egg-info/top_level.txt
6
+ src/pythoniota/__init__.py
7
+ src/pythoniota/_bitflag.py
8
+ src/pythoniota/_compat.py
9
+ src/pythoniota/_safe_eval.py
10
+ src/pythoniota/enum.py
11
+ src/pythoniota/sequence.py
12
+ tests/test_bitflags.py
13
+ tests/test_compat.py
14
+ tests/test_enum.py
15
+ tests/test_integration.py
16
+ tests/test_safe_eval.py
17
+ tests/test_sequence.py
@@ -0,0 +1 @@
1
+ pythoniota
@@ -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
+ ]
@@ -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())
@@ -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
@@ -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
@@ -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