PythonIota 1.7.1__tar.gz → 1.8.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.
- pythoniota-1.8.0/MANIFEST.in +2 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/PKG-INFO +143 -12
- pythoniota-1.7.1/src/PythonIota.egg-info/PKG-INFO → pythoniota-1.8.0/README.md +538 -424
- {pythoniota-1.7.1 → pythoniota-1.8.0}/pyproject.toml +4 -1
- pythoniota-1.7.1/README.md → pythoniota-1.8.0/src/PythonIota.egg-info/PKG-INFO +555 -407
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/PythonIota.egg-info/SOURCES.txt +9 -1
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/__init__.py +3 -2
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/__init__.py +13 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_argument.py +32 -8
- pythoniota-1.8.0/src/pythoniota/cli/_completion.py +112 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_parser.py +126 -51
- pythoniota-1.8.0/src/pythoniota/cli/_sources.py +129 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/enum.py +11 -1
- pythoniota-1.8.0/src/pythoniota/regex/__init__.py +74 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/regex/_compiler.py +5 -7
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/regex/_pattern.py +94 -32
- pythoniota-1.8.0/src/pythoniota/regex/_vm.py +196 -0
- pythoniota-1.8.0/src/pythoniota/sequence.py +658 -0
- pythoniota-1.8.0/tests/__init__.py +0 -0
- pythoniota-1.8.0/tests/test_cli.py +558 -0
- pythoniota-1.8.0/tests/test_cli_completion.py +271 -0
- pythoniota-1.8.0/tests/test_cli_sources.py +431 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_enum_enhanced.py +41 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_integration.py +5 -1
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_recipes.py +65 -1
- pythoniota-1.8.0/tests/test_regex_budget.py +163 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_regex_extended.py +39 -0
- pythoniota-1.8.0/tests/test_sequence.py +576 -0
- pythoniota-1.8.0/tests/test_sequence_typing.py +167 -0
- pythoniota-1.8.0/tests/test_workflows.py +33 -0
- pythoniota-1.7.1/MANIFEST.in +0 -1
- pythoniota-1.7.1/src/pythoniota/regex/__init__.py +0 -66
- pythoniota-1.7.1/src/pythoniota/regex/_vm.py +0 -129
- pythoniota-1.7.1/src/pythoniota/sequence.py +0 -461
- pythoniota-1.7.1/tests/test_cli.py +0 -244
- pythoniota-1.7.1/tests/test_sequence.py +0 -153
- {pythoniota-1.7.1 → pythoniota-1.8.0}/setup.cfg +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/PythonIota.egg-info/dependency_links.txt +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/PythonIota.egg-info/top_level.txt +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/_bitflag.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/_compat.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/_safe_eval.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_errors.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_formatter.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_namespace.py +0 -0
- /pythoniota-1.7.1/tests/__init__.py → /pythoniota-1.8.0/src/pythoniota/py.typed +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/recipes.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/regex/_parser.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_advanced.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_bitflags.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_compat.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_coverage_fill.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_coverage_fill2.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_coverage_fill3.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_enum.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_new_features.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_regex.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_safe_eval.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_serialization.py +0 -0
- {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_string_enum.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: PythonIota
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: A zero-dependency Python toolkit: Go-style iota enums, sequence generators, a from-scratch regex engine, and an argparse-style CLI parser
|
|
5
5
|
Author: Equinox
|
|
6
6
|
License: MIT
|
|
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
# PythonIota
|
|
19
19
|
|
|
20
20
|
A **zero-dependency Python toolkit** of from-scratch building blocks — Go-style
|
|
21
|
-
`iota` enumerations, lazy sequence generators, and a
|
|
21
|
+
`iota` enumerations, lazy sequence generators, and a Pike-VM regex engine.
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
pip install PythonIota
|
|
@@ -26,6 +26,26 @@ pip install PythonIota
|
|
|
26
26
|
|
|
27
27
|
Requires Python 3.10+.
|
|
28
28
|
|
|
29
|
+
## 1.8.0
|
|
30
|
+
|
|
31
|
+
### New features
|
|
32
|
+
|
|
33
|
+
- Generic, single-pass `IotaIterator` pipelines support continuous chaining.
|
|
34
|
+
- CLI options support explicit JSON/mapping configuration, opt-in environment
|
|
35
|
+
values and metadata-only completion candidates.
|
|
36
|
+
- Regex operations support shared step/time budgets and structured diagnostics.
|
|
37
|
+
|
|
38
|
+
### Fixes
|
|
39
|
+
|
|
40
|
+
- Mapping derived sequences preserves their lazy iteration, element types and
|
|
41
|
+
finite/infinite behavior; `filter` still returns an iterator.
|
|
42
|
+
- Empty regex matches, conditional capture states and zero-width repetitions
|
|
43
|
+
retain the appropriate alternatives and iteration order.
|
|
44
|
+
- Parent CLI positionals are validated before subcommand dispatch.
|
|
45
|
+
- Enum aliases cannot overwrite existing or reserved names; alias identifiers
|
|
46
|
+
must already be NFKC-normalized to agree with Python attribute syntax.
|
|
47
|
+
- Sequence APIs carry generic element types, and distributions include `py.typed`.
|
|
48
|
+
|
|
29
49
|
## 1.7.1 fixes
|
|
30
50
|
|
|
31
51
|
- Full matching now considers complete alternatives instead of rejecting a
|
|
@@ -109,6 +129,10 @@ Color.has('Red') # True
|
|
|
109
129
|
Color.get('Nope', -1) # -1
|
|
110
130
|
```
|
|
111
131
|
|
|
132
|
+
Alias names must be public Python identifiers. Existing members, aliases,
|
|
133
|
+
methods and reserved/internal names cannot be overwritten; rejected aliases
|
|
134
|
+
leave the enum unchanged.
|
|
135
|
+
|
|
112
136
|
### `@unique` — reject duplicate values
|
|
113
137
|
|
|
114
138
|
```python
|
|
@@ -251,9 +275,33 @@ seq.flatten() # flatten one level
|
|
|
251
275
|
iota().enumerate() .accumulate() .zip(other)
|
|
252
276
|
```
|
|
253
277
|
|
|
278
|
+
### Chainable iterator pipelines
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from pythoniota import IotaIterator, iota
|
|
282
|
+
|
|
283
|
+
windows = iota(20).filter(lambda x: x % 2 == 0).map(str).window(3)
|
|
284
|
+
windows.take(2) # [('0', '2', '4'), ('2', '4', '6')]
|
|
285
|
+
next(windows) # ('4', '6', '8'): continues the same stream
|
|
286
|
+
IotaIterator([1, 2, 3]).map(lambda x: x * 10).to_list() # [10, 20, 30]
|
|
287
|
+
iota().filter(lambda x: x % 2 == 0).map(str).take(3) # ['0', '2', '4']
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`filter`, `accumulate`, `enumerate`, `zip`, `chunk`, `window`, `takewhile`,
|
|
291
|
+
`dropwhile`, `pairwise`, `flatten` and `distinct` return `IotaIterator` pipelines,
|
|
292
|
+
which can continue chaining. They are lazy and single-pass (`iter(pipe) is pipe`),
|
|
293
|
+
with element types preserved through generic transformations. `take(n)` still
|
|
294
|
+
returns a list. Chunk/window sizes must be positive and are checked before consumption.
|
|
295
|
+
|
|
296
|
+
Base sequences and their `.map()` results remain reusable. Iterator pipelines
|
|
297
|
+
have no length or infinite-status metadata: exhaustive terminals such as
|
|
298
|
+
`to_list()`, `sum()` and `last()` can run forever on an unbounded stream. Take a
|
|
299
|
+
bounded prefix first when needed.
|
|
300
|
+
|
|
254
301
|
### Terminal operations
|
|
255
302
|
|
|
256
|
-
Reductions on plain arithmetic sequences are O(1); infinite sequences
|
|
303
|
+
Reductions on plain arithmetic sequences are O(1); known-infinite base sequences
|
|
304
|
+
reject exhaustive terminals. Iterator pipelines consume their remaining elements.
|
|
257
305
|
|
|
258
306
|
```python
|
|
259
307
|
iota(1, 101).sum() # 5050 (closed-form, O(1))
|
|
@@ -327,10 +375,12 @@ safe_eval("a * b + 1", {"a": 3, "b": 4}) # 13
|
|
|
327
375
|
|
|
328
376
|
## Regex
|
|
329
377
|
|
|
330
|
-
A from-scratch regular-expression engine
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
378
|
+
A from-scratch regular-expression engine that runs bytecode with an ordered
|
|
379
|
+
**Pike VM**. Plain patterns such as `(a+)+$` avoid catastrophic backtracking and
|
|
380
|
+
run in linear time in the text length for a fixed pattern. This is not a
|
|
381
|
+
universal complexity guarantee: lookaround can rescan suffixes, and conditionals
|
|
382
|
+
can retain multiple capture-participation states. Use explicit `max_steps` and
|
|
383
|
+
`timeout` limits when matching potentially expensive patterns.
|
|
334
384
|
|
|
335
385
|
```python
|
|
336
386
|
from pythoniota import regex
|
|
@@ -361,15 +411,46 @@ regex.findall(r"[a-z]+", "AbCdEf", regex.IGNORECASE) # ['AbCdEf']
|
|
|
361
411
|
regex.findall(r"\d+(?=px)", "10px 20em 30px") # ['10', '30'] (lookahead)
|
|
362
412
|
```
|
|
363
413
|
|
|
364
|
-
Backreferences (`\1`) are intentionally
|
|
365
|
-
|
|
366
|
-
|
|
414
|
+
Backreferences (`\1`) are intentionally not supported; this is an NFA-based
|
|
415
|
+
engine rather than a general backtracking engine. Empty matches follow the
|
|
416
|
+
iteration and splitting behavior of Python 3.10–3.13 `re`, including a non-empty
|
|
417
|
+
match immediately after an empty one at the same position.
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
regex.search(r"(a+)+$", "a" * 40 + "!") # None, without backtracking
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Execution limits and diagnostics
|
|
367
424
|
|
|
368
425
|
```python
|
|
369
|
-
|
|
370
|
-
|
|
426
|
+
pattern = regex.compile(r"(?=a*$)")
|
|
427
|
+
try:
|
|
428
|
+
result = pattern.search("a" * 100 + "!", max_steps=1000, timeout=0.1)
|
|
429
|
+
except regex.RegexLimitError as error:
|
|
430
|
+
print(error.reason, error.steps)
|
|
431
|
+
|
|
432
|
+
structure = pattern.explain() # JSON-serializable bytecode, groups and nested assertions
|
|
433
|
+
report = pattern.diagnose("aaaa!", max_steps=1000)
|
|
434
|
+
print(report["status"], report["steps"], report["op_counts"])
|
|
371
435
|
```
|
|
372
436
|
|
|
437
|
+
All matching, iteration, splitting and replacement functions accept keyword-only
|
|
438
|
+
`max_steps` (nonnegative integer) and `timeout` (nonnegative seconds), defaulting
|
|
439
|
+
to no limits. A step is a bytecode analysis/visit, including epsilon closure and
|
|
440
|
+
consuming states; counts are implementation-specific, not a portable time unit.
|
|
441
|
+
Nested lookarounds and all matches within one call share a single budget.
|
|
442
|
+
`finditer` starts its timeout when created, including pauses between yields.
|
|
443
|
+
|
|
444
|
+
Limit exhaustion raises `RegexLimitError`, never a false no-match result.
|
|
445
|
+
`diagnose` supports `match`, `fullmatch` and `search`, instead returning an explicit
|
|
446
|
+
`limit_exceeded` status with its reason, counters, peak pending threads and elapsed
|
|
447
|
+
time. `explain` and `diagnose` also exist as module-level functions.
|
|
448
|
+
|
|
449
|
+
Limits apply after pattern compilation. Timeouts are cooperative: they cannot
|
|
450
|
+
preempt Python replacement callbacks, compilation or a single long native
|
|
451
|
+
operation; elapsed time is checked again after callbacks. For hard isolation of
|
|
452
|
+
untrusted patterns/callbacks, use a separate process and external resource limits.
|
|
453
|
+
|
|
373
454
|
---
|
|
374
455
|
|
|
375
456
|
## CLI
|
|
@@ -417,6 +498,56 @@ args = p.parse_args(["add", "README.md", "-f"])
|
|
|
417
498
|
args.cmd, args.path, args.force # ('add', 'README.md', True)
|
|
418
499
|
```
|
|
419
500
|
|
|
501
|
+
Parent positionals declared before `add_subparsers` are consumed and validated
|
|
502
|
+
before the command. Parent options may occur before the command; trailing
|
|
503
|
+
options are parsed only by the selected child. As with argparse, variable
|
|
504
|
+
`nargs` are greedy and command names are not special delimiters, so prefer
|
|
505
|
+
fixed-count parent positionals for predictable subcommand syntax.
|
|
506
|
+
|
|
507
|
+
### Configuration and environment values
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
p = cli.ArgumentParser(config={"port": 8000, "mode": "fast"}, env_prefix="APP_")
|
|
511
|
+
p.add_argument("--port", type=int, default=3000)
|
|
512
|
+
p.add_argument("--mode", choices=["fast", "slow"])
|
|
513
|
+
p.add_argument("--token", env_var="SERVICE_TOKEN")
|
|
514
|
+
args = p.parse_args(["--port", "9000"]) # --port overrides APP_PORT and config
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
`config` accepts a mapping or a UTF-8 JSON-object path, read again on each parse.
|
|
518
|
+
Keys are this parser's optional-argument destinations, not option spellings;
|
|
519
|
+
unknown keys and malformed files are errors. Sources do not fill positionals.
|
|
520
|
+
Precedence is explicit CLI > enabled environment > config > argument default;
|
|
521
|
+
only the winning value is converted and checked against `choices`. Required
|
|
522
|
+
options may be supplied by config/environment. Explicit caller `Namespace`
|
|
523
|
+
values are retained unless the CLI changes them, but do not by themselves
|
|
524
|
+
satisfy required options.
|
|
525
|
+
|
|
526
|
+
Environment lookup is opt-in via `env_prefix` or an exact `env_var`; children
|
|
527
|
+
configure their own sources with `add_parser(config=..., env_prefix=...)`.
|
|
528
|
+
Scalar environment values are literal strings. Multi-value options use JSON
|
|
529
|
+
arrays; `append` uses an array of occurrences (nested arrays for multi-value
|
|
530
|
+
occurrences). Boolean values accept true/false, yes/no, on/off and 1/0; counts
|
|
531
|
+
must be nonnegative integers. Explicit CLI append/count replaces lower sources,
|
|
532
|
+
while legacy accumulation from argument defaults is retained when no external
|
|
533
|
+
source applies. No configuration contents are executed as code or shell text.
|
|
534
|
+
|
|
535
|
+
### Completion candidates
|
|
536
|
+
|
|
537
|
+
```python
|
|
538
|
+
p.complete([], "--po") # ['--port']
|
|
539
|
+
p.complete(["--mode"], "s") # ['slow']
|
|
540
|
+
p.complete([], "--mode=f") # ['--mode=fast']
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
`complete(tokens, prefix="")` accepts finished words without the program/current
|
|
544
|
+
word, then returns sorted, unique full replacement words for options, choices
|
|
545
|
+
and subcommands. It does not parse, read config/environment, call type converters,
|
|
546
|
+
execute shells or mutate parse state. Variable-length positionals are best-effort;
|
|
547
|
+
only collection-backed choices are enumerated, not generators. This is an adapter
|
|
548
|
+
API for shells/editors: it does not install shell hooks, quote candidates or
|
|
549
|
+
perform filesystem completion.
|
|
550
|
+
|
|
420
551
|
---
|
|
421
552
|
|
|
422
553
|
## License
|