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.
Files changed (60) hide show
  1. pythoniota-1.8.0/MANIFEST.in +2 -0
  2. {pythoniota-1.7.1 → pythoniota-1.8.0}/PKG-INFO +143 -12
  3. pythoniota-1.7.1/src/PythonIota.egg-info/PKG-INFO → pythoniota-1.8.0/README.md +538 -424
  4. {pythoniota-1.7.1 → pythoniota-1.8.0}/pyproject.toml +4 -1
  5. pythoniota-1.7.1/README.md → pythoniota-1.8.0/src/PythonIota.egg-info/PKG-INFO +555 -407
  6. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/PythonIota.egg-info/SOURCES.txt +9 -1
  7. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/__init__.py +3 -2
  8. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/__init__.py +13 -0
  9. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_argument.py +32 -8
  10. pythoniota-1.8.0/src/pythoniota/cli/_completion.py +112 -0
  11. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_parser.py +126 -51
  12. pythoniota-1.8.0/src/pythoniota/cli/_sources.py +129 -0
  13. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/enum.py +11 -1
  14. pythoniota-1.8.0/src/pythoniota/regex/__init__.py +74 -0
  15. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/regex/_compiler.py +5 -7
  16. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/regex/_pattern.py +94 -32
  17. pythoniota-1.8.0/src/pythoniota/regex/_vm.py +196 -0
  18. pythoniota-1.8.0/src/pythoniota/sequence.py +658 -0
  19. pythoniota-1.8.0/tests/__init__.py +0 -0
  20. pythoniota-1.8.0/tests/test_cli.py +558 -0
  21. pythoniota-1.8.0/tests/test_cli_completion.py +271 -0
  22. pythoniota-1.8.0/tests/test_cli_sources.py +431 -0
  23. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_enum_enhanced.py +41 -0
  24. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_integration.py +5 -1
  25. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_recipes.py +65 -1
  26. pythoniota-1.8.0/tests/test_regex_budget.py +163 -0
  27. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_regex_extended.py +39 -0
  28. pythoniota-1.8.0/tests/test_sequence.py +576 -0
  29. pythoniota-1.8.0/tests/test_sequence_typing.py +167 -0
  30. pythoniota-1.8.0/tests/test_workflows.py +33 -0
  31. pythoniota-1.7.1/MANIFEST.in +0 -1
  32. pythoniota-1.7.1/src/pythoniota/regex/__init__.py +0 -66
  33. pythoniota-1.7.1/src/pythoniota/regex/_vm.py +0 -129
  34. pythoniota-1.7.1/src/pythoniota/sequence.py +0 -461
  35. pythoniota-1.7.1/tests/test_cli.py +0 -244
  36. pythoniota-1.7.1/tests/test_sequence.py +0 -153
  37. {pythoniota-1.7.1 → pythoniota-1.8.0}/setup.cfg +0 -0
  38. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/PythonIota.egg-info/dependency_links.txt +0 -0
  39. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/PythonIota.egg-info/top_level.txt +0 -0
  40. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/_bitflag.py +0 -0
  41. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/_compat.py +0 -0
  42. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/_safe_eval.py +0 -0
  43. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_errors.py +0 -0
  44. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_formatter.py +0 -0
  45. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/cli/_namespace.py +0 -0
  46. /pythoniota-1.7.1/tests/__init__.py → /pythoniota-1.8.0/src/pythoniota/py.typed +0 -0
  47. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/recipes.py +0 -0
  48. {pythoniota-1.7.1 → pythoniota-1.8.0}/src/pythoniota/regex/_parser.py +0 -0
  49. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_advanced.py +0 -0
  50. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_bitflags.py +0 -0
  51. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_compat.py +0 -0
  52. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_coverage_fill.py +0 -0
  53. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_coverage_fill2.py +0 -0
  54. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_coverage_fill3.py +0 -0
  55. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_enum.py +0 -0
  56. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_new_features.py +0 -0
  57. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_regex.py +0 -0
  58. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_safe_eval.py +0 -0
  59. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_serialization.py +0 -0
  60. {pythoniota-1.7.1 → pythoniota-1.8.0}/tests/test_string_enum.py +0 -0
@@ -0,0 +1,2 @@
1
+ recursive-include tests *.py
2
+ include src/pythoniota/py.typed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PythonIota
3
- Version: 1.7.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 linear-time regex engine.
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 raise where they would not terminate.
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. Unlike Python's backtracking `re`, it
331
- compiles the pattern to a tiny bytecode and runs it with a **Pike VM** (a
332
- thread-list NFA simulation), so matching is linear in `len(text) × len(program)`
333
- — pathological patterns like `(a+)+$` finish instantly instead of hanging.
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 *not* supported: they can't be matched
365
- by a finite automaton in linear time — that's the price of the no-backtracking
366
- guarantee (the same trade-off Go's `regexp` and RE2 make).
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
- # no ReDoS: this returns in microseconds, where re takes ~13s at length 28
370
- regex.search(r"(a+)+$", "a" * 40 + "!") # None, instantly
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