Metadata-Version: 2.5
Name: itrx
Version: 0.5.0
Summary: A chainable iterator adapter
Project-URL: Homepage, https://github.com/virgesmith/itrx
Project-URL: Bug Tracker, https://github.com/virgesmith/itrx/issues
Author-email: virgesmith <andrew@friarswood.net>
License-Expression: MIT
License-File: LICENCE.md
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Provides-Extra: examples
Requires-Dist: ipykernel; extra == 'examples'
Description-Content-Type: text/markdown

# `itrx`: A Chainable Iterable Adaptor

![PyPI - Version](https://img.shields.io/pypi/v/itrx)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/itrx)
![PyPI - License](https://img.shields.io/pypi/l/itrx)

`itrx` is a Python library that adapts iterators, iterables, and generators, providing a Rust-inspired `Iterator` trait experience with added Pythonic conveniences. It enables developers to build complex data processing pipelines with a fluent, chainable, and lazy API. In most cases, it simply wraps `itertools` and/or builtins in syntactic sugar.

Heavily inspired by Rust's [Iterator trait](https://doc.rust-lang.org/std/iter/trait.Iterator.html), `itrx` offers a familiar and robust pattern for sequence manipulation.

## Key Features & Why `Itr`?

*   **Compatibility:** `Itr` is designed to work seamlessly with various Python iterable types, including sequences, ranges, custom iterators, and generators.
*   **Fluent & Chainable API:** Write expressive, left-to-right sequences of operations on your data.
*   **Lazy Evaluation:** Operations are only performed when their results are needed, improving efficiency for large or infinite sequences.
*   **Rust-Inspired Patterns:** Bring the power and clarity of Rust's `Iterator` design to your Python projects.
*   **Pythonic Additions:** Includes convenient methods like `starmap` that integrate smoothly with Python's ecosystem.
*   **Simplifies Complex Logic:** Often provides a more readable and concise alternative to nested `itertools` or built-in functions.


## Installation

```sh
pip install itrx
```

> **Using an AI coding agent?** `itrx` ships an installable [agent skill](#agent-skill) — run
> `uv run itrx-skill --install` and your agent gets a built-in reference for writing `Itr` chains
> correctly, without needing this whole README in context.

## Quick Start & Core Functionality

### Chaining

`Itr` wraps Python Iterators, Iterables, and Generators, allowing efficient chaining methods for data transformation.
In this completely arbitrary example we take some integers, reverse them, discard some, take every 4th value and
print it if its square ends with the digit 9:

```py
>>> from itrx import Itr
>>> Itr(range(100)).rev().step_by(4).skip(10).map(lambda x: x * x).filter(lambda x: x % 10 == 9).for_each(print)
2209
1849
729
529
49
9

```

For reference, the equivalent expression using built-ins and `itertools` can be significantly less readable:

```python
from itertools import islice

for item in filter(
    lambda x: x % 10 == 9,
    map(lambda x: x * x, islice(islice(reversed(range(100)), None, None, 4), 10, None)),
):
    print(item)

```

### Outputs

While `Itr` methods typically return `self` or another `Itr` instance, the `collect()` method allows you to materialize the results into various Python collections: `tuple` (default), `list`, `set`, or `dict`.

Here's how to group words by their length into a dictionary:

```python
>>> from itrx import Itr
>>> Itr(("apple", "banana", "carrot")).groupby(len).collect(dict)
{5: ('apple',), 6: ('banana', 'carrot')}

```

For reference, the equivalent using `itertools` directly (is fairly readable):

```py
>>> import itertools
>>> {k: tuple(v) for k, v in itertools.groupby(("apple", "banana", "carrot"), key=len)}
{5: ('apple',), 6: ('banana', 'carrot')}


```

Note:
1. Using `collect(dict)` requires an iterable that produces 2-tuples (key-value pairs).
2. `collect(set)` gives the distinct items but loses their order; `unique()` lazily keeps the first occurrence of each, in order.

## How `Itr` Works: Lazy vs. Eager

Most `Itr` methods are **lazy transformations**, meaning they return a new `Itr` instance without immediately processing any data. This allows for arbitrary chaining and efficient memory usage, as items are only processed as they are requested. In most cases, `Itr` simply acts as a convenient wrapper around `itertools`, enabling this left-to-right chaining syntax.

- **Combining and splitting:**  `partition`, `copy`, `batched`, `pairwise`, `rolling`, `chain`, `cycle`, `repeat`, `product`, `inspect`, `intersperse`, `interleave`, `chunk_by`, `zip`, `zip_longest`
- **Transformation and filtering:** `accumulate`, `filter`, `filter_map`, `compress`, `map`, `starmap`, `map_while`, `flatten`, `flat_map`, `skip_while`, `take_while`, `dedup`, `dedup_with_count`, `unique`, `scan`

However, some methods are **eager consumers**. These methods iterate over and consume the underlying data, returning concrete values, collections, or aggregates. Examples include:

*   **Collection methods:** `collect`, `last`, `next`, `next_chunk`, `next_if`, `nth`, `position`
*   **Aggregation methods:** `count`, `reduce`, `max`, `min`, `min_max`, `sum`, `prod`, `all`, `any`, `consume`, `find`, `find_map`, `fold`, `eq`, `is_sorted`
*   **Sorting/grouping:** `sorted_by` and `groupby` sort the entire input up front, and `value_counts` counts it (most common first, like pandas), so all three consume the whole iterator immediately and must not be used on infinite sources. Use the lazy `chunk_by` to group consecutive runs without sorting. Likewise `rev` and `take_last` must read to the end of the input before yielding anything.

### Important Considerations

When working with `Itr`, keep these points in mind:

*   **Single-Pass Iterators:** Like all Python iterators, `Itr` instances (and their underlying iterators) can generally only be consumed once. If you need to process the same sequence multiple times, use methods like `copy()`, `cycle()`, or `repeat()` as necessary.
*   **No Rewinding:** It's not possible to rewind an `Itr` to an earlier state. You can "preview" the next value using the `peek()` method, and conditionally consume it with `next_if()`.
*   **Missing Items:** `find`, `find_map` and `position` return `None` when nothing matches, while `peek`, `last`, `max`, `min` and `min_max` raise `ValueError` on an empty iterator. As with their builtin counterparts, `nth` raises `IndexError` when the iterator is too short (like indexing a sequence), and `reduce` raises `TypeError` when it is empty (like `functools.reduce`). Only `next()` raises `StopIteration`, like the builtin, so avoid calling it inside a `map` or `filter` callback: a `StopIteration` escaping a callback silently ends the enclosing iteration rather than raising.
*   **Infinite Iterators:** Be cautious with open-ended iterators (e.g., those from `itertools.count()` or custom generators). Eager evaluation methods (like `collect()`, `count()`, `reduce()`) will attempt to consume the entire sequence, potentially leading to infinite loops or out-of-memory errors if applied to an infinite source.

## Agent skill

`itrx` ships a `SKILL.md` for AI coding agents (e.g. Claude Code) covering the `Itr` API, the
lazy/eager split described above, and the common pitfalls (single-pass iterators, infinite
sources, `groupby` vs `chunk_by`), so an agent doesn't need this whole README in context to write
correct `Itr` chains.

Install it into a project as a symlink to the version installed in the current environment:

```sh
uv run itrx-skill --install [PATH]  # default PATH: .agents
uv run itrx-skill --remove [PATH]   # default PATH: .agents
```

(Or, without `uv`, activate the virtualenv `itrx` is installed in and drop the `uv run` prefix —
`itrx-skill` is a normal console-script entry point, so it's only on `PATH` while that environment
is active.)

This creates (or removes) `PATH/skills/itrx`, symlinked to the skill bundled inside the installed
`itrx` package, so it always matches the version in use.

## API Reference

For a complete list of `Itr` methods and their detailed descriptions, please refer to the [API documentation](./doc/apidoc.md).

*Note: `apidoc.md` is auto-generated using `Itr` - see [introspect.py](src/scripts/introspect.py).*

## Examples

Some worked examples can be found [in this notebook](./doc/examples.ipynb). Use e.g. `pip install itrx[examples]` to resolve the extra dependencies.
