Metadata-Version: 2.4
Name: PythonIota
Version: 1.3.0
Summary: Go-style iota enumerations and flexible sequence generators for Python
Author: Equinox
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# PythonIota

Go-style `iota` enumerations and flexible, lazy sequence generators for Python — zero dependencies, fully typed.

```bash
pip install PythonIota
```

Requires Python 3.10+.

---

## Enums

### Go-style `iota`

`iota` starts at `0` and auto-increments each time it is read. Literal assignments do **not** consume it.

```python
from pythoniota import IotaEnum

class Color(IotaEnum):
    Red   = iota   # 0
    Green = iota   # 1
    Blue  = iota   # 2

Color.Green          # 1
Color.names()        # ['Red', 'Green', 'Blue']
Color.values()       # [0, 1, 2]
list(Color)          # [('Red', 0), ('Green', 1), ('Blue', 2)]
0 in Color           # True (by value)
'Red' in Color       # True (by name)
Color.from_value(2)  # 'Blue'
```

`iota` works inside expressions — the value is the current counter:

```python
class Perm(IotaEnum):
    Read  = 1 << iota          # 1
    Write = 1 << iota          # 2
    Exec  = 1 << iota          # 4
    All   = Read | Write | Exec # 7

class Size(IotaEnum):
    KB = 1 << (iota + 10)      # 1024
    MB = 1 << (iota + 10)      # 2048
    GB = 1 << (iota + 10)      # 4096
```

### Skipping values & custom start/step

```python
class Errno(IotaEnum):
    EPERM = iota   # 0
    skip(2)        # skip 1, 2
    EBADF = iota   # 3

class Port(IotaEnum):
    _iota_start_ = 8000
    _iota_step_  = 10
    HTTP  = iota   # 8000
    HTTPS = iota   # 8010
```

`_ = iota` also skips a single value (Go's blank identifier).

### Immutability, aliases, serialization

```python
Color.Red = 99          # AttributeError: cannot modify enum member
Color.alias('R', 'Red') # Color.R == 0
Color.to_dict()         # {'Red': 0, 'Green': 1, 'Blue': 2}
Color.to_json()         # '{"Red": 0, "Green": 1, "Blue": 2}'
Color.from_json(s)      # -> dict of members
Color.has('Red')        # True
Color.get('Nope', -1)   # -1
```

### `@unique` — reject duplicate values

```python
from pythoniota import unique

@unique
class Status(IotaEnum):
    Active   = iota
    Inactive = iota
# raises ValueError if two members share a value
```

### Ordered enums

Add `_ordered_ = True` to make members comparable by declaration order:

```python
class Priority(IotaEnum):
    _ordered_ = True
    Low    = iota
    Medium = iota
    High   = iota

Priority.Low < Priority.High   # True
```

### Bit flags

```python
from pythoniota import IotaBitFlags, FlagScope

class Access(IotaBitFlags):
    Read  = 1 << iota   # 1
    Write = 1 << iota   # 2
    Exec  = 1 << iota   # 4

flags = Access.Read | Access.Write
flags.has(Access.Read)              # True
flags.has_all(Access.Read, Access.Exec)  # False
flags.has_any(Access.Read, Access.Exec)  # True
list(flags.decompose())             # [BitFlag(1), BitFlag(2)]

with FlagScope() as scope:
    scope.grant(Access.Read, Access.Write)
    scope.revoke(Access.Write)
    scope.has(Access.Read)          # True
```

### String enums

Members resolve to their own name, or a custom format:

```python
from pythoniota import IotaStringEnum

class Color(IotaStringEnum):
    Red  = iota   # 'Red'
    Blue = iota   # 'Blue'

class Code(IotaStringEnum):
    _format_ = "{name}_{index:03d}"
    OK    = iota  # 'OK_000'
    Error = iota  # 'Error_001'
```

### `@iota_enum` decorator

Turn a plain class into an enum:

```python
from pythoniota import iota_enum

@iota_enum
class Color:
    Red = 0
    Green = 1
    Blue = 2

@iota_enum(ordered=True)
class Priority:
    Low = 0
    High = 1
```

### `match` / `case`

Enum members match by value:

```python
match color:
    case Color.Red:   ...
    case Color.Blue:  ...
    case _:           ...
```

---

## Sequences

`iota(...)` is a lazy, composable sequence. Construction mirrors `range`, plus an optional `map`:

```python
from pythoniota import iota

iota()                       # 0, 1, 2, ...        (infinite)
iota(5)                      # 0, 1, 2, 3, 4
iota(2, 10, 2)               # 2, 4, 6, 8
iota(5, map=lambda i: i*i)   # 0, 1, 4, 9, 16

s = iota(10)
len(s)          # 10        (O(1))
s[7]            # 7         (O(1) arithmetic indexing)
s[2:5]          # [2, 3, 4]
list(reversed(s))   # 9, 8, ... 0   (lazy)
5 in s          # True      (O(1))
s.take(3)       # [0, 1, 2]
```

### Operators

```python
iota(3) + iota(3, 6)   # concat: 0,1,2,3,4,5
iota(3) * 2            # repeat: 0,1,2,0,1,2
iota(3) | iota(3, 6)   # interleave: 0,3,1,4,2,5
iota(3) @ iota(3, 6)   # zip: (0,3),(1,4),(2,5)
```

### Lazy combinators

```python
iota(10).filter(lambda x: x % 2 == 0)   # 0,2,4,6,8
iota().map(lambda x: x*x)               # 0,1,4,9,...
iota().takewhile(lambda x: x < 5)       # 0,1,2,3,4
iota(10).dropwhile(lambda x: x < 7)     # 7,8,9
iota(4).pairwise()                      # (0,1),(1,2),(2,3)
iota(5).window(3)                       # (0,1,2),(1,2,3),(2,3,4)
iota(7).chunk(3)                        # [0,1,2],[3,4,5],[6]
iota(6).map(lambda x: x % 3).distinct() # 0,1,2
seq.flatten()                           # flatten one level
iota().enumerate()  .accumulate()  .zip(other)
```

### Terminal operations

Reductions on plain arithmetic sequences are O(1); infinite sequences raise where they would not terminate.

```python
iota(1, 101).sum()      # 5050   (closed-form, O(1))
iota(3, 10).min()       # 3
iota(3, 10).max()       # 9
iota(0, 10, 2).count()  # 5
iota(5).last()          # 4
iota(10).nth(3)         # 3
iota().first()          # 0
iota().find(lambda x: x > 100)   # 101
iota(1, 5).all(lambda x: x > 0)  # True
iota(5).any(lambda x: x == 3)    # True
iota(3).reduce(lambda a, b: a + b)  # 3

iota(3).to_list()                     # [0, 1, 2]
iota(3).to_tuple()                    # (0, 1, 2)
iota(3).to_set()                      # {0, 1, 2}
iota(3).to_dict(value=lambda x: x*x)  # {0: 0, 1: 1, 2: 4}
```

### Async iteration

```python
async for x in iota(5):
    ...
```

---

## Recipes

```python
from pythoniota.recipes import (
    fibonacci, lucas, factorial, catalan, harmonic,
    triangle, powers, geometric, primes, primes_sieve,
    pascal_row, collatz, repeat, cycle,
)

fibonacci(10).to_list()   # 0,1,1,2,3,5,8,13,21,34
lucas(7).to_list()        # 2,1,3,4,7,11,18
factorial(6).to_list()    # 1,1,2,6,24,120
catalan(6).to_list()      # 1,1,2,5,14,42
harmonic(4).to_list()     # 1.0, 1.5, 1.833..., 2.083...
triangle(5).to_list()     # 0,1,3,6,10
powers(2, 8).to_list()    # 1,2,4,...,128
geometric(3, 2, 4).to_list()  # 3,6,12,24
primes(5).to_list()       # 2,3,5,7,11        (first n primes)
primes_sieve(20).to_list()# 2,3,5,7,11,13,17,19  (< limit, Eratosthenes)
pascal_row(4).to_list()   # 1,4,6,4,1
collatz(6).to_list()      # 6,3,10,5,16,8,4,2,1
repeat('x', 3).to_list()  # ['x','x','x']
cycle([1,2], 5).to_list() # 1,2,1,2,1
```

Recipes without a count argument produce infinite sequences: `fibonacci().take(20)`, `primes().takewhile(lambda p: p < 100)`.

---

## Safe expression evaluation

`safe_eval` evaluates arithmetic expressions over a whitelisted AST (no `eval`, no names/calls beyond provided variables):

```python
from pythoniota import safe_eval

safe_eval("2 ** 10")                 # 1024
safe_eval("a * b + 1", {"a": 3, "b": 4})  # 13
```

---

## License

MIT
