diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml
index ec593ee..1ec10ab 100644
--- a/.github/workflows/check-docs.yml
+++ b/.github/workflows/check-docs.yml
@@ -16,15 +16,16 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
with:
- python-version: "3.11"
+ enable-cache: true
+
+ - name: Set up Python
+ run: uv python install
- - name: Install hatch
- run: |
- python -m pip install --upgrade pip
- python -m pip install --upgrade hatch
+ - name: Install the project
+ run: uv sync --locked --group docs
- name: Build docs
- run: hatch run docs:build
+ run: uv run zensical build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 8b8c644..a85d791 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,15 +16,19 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
with:
- python-version: "3.11"
+ enable-cache: true
- - name: Install hatch
- run: |
- python -m pip install --upgrade pip
- python -m pip install --upgrade hatch
+ - name: Set up Python
+ run: uv python install
+
+ - name: Install the project
+ run: uv sync --locked --group dev
- name: Run linters
- run: hatch run lint:all
+ run: |
+ uv run ruff check .
+ uv run ruff format --check .
+ uv run pyright src/iterum type_tests
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 6083833..576863a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,20 +16,21 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
- python-version: ['3.11']
+ python-version: ['3.14']
steps:
- uses: actions/checkout@v3
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
with:
- python-version: ${{ matrix.python-version }}
+ enable-cache: true
+
+ - name: Set up Python ${{ matrix.python-version }}
+ run: uv python install ${{ matrix.python-version }}
- - name: Install hatch
- run: |
- python -m pip install --upgrade pip
- python -m pip install --upgrade hatch
+ - name: Install the project
+ run: uv sync --locked --group dev
- name: Run tests
- run: hatch run test:cov
+ run: uv run pytest --cov=src/iterum --cov-config=pyproject.toml --cov-report=term-missing --doctest-modules -vvvx
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index 974a0c9..0000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-repos:
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
- hooks:
- - id: trailing-whitespace
- - id: end-of-file-fixer
- - id: check-yaml
- - id: debug-statements
-
- - repo: https://github.com/asottile/reorder_python_imports
- rev: v3.8.2
- hooks:
- - id: reorder-python-imports
- exclude: __init__.py
-
- - repo: https://github.com/psf/black
- rev: 22.6.0
- hooks:
- - id: black
-
- - repo: https://github.com/astral-sh/ruff-pre-commit
- # Ruff version.
- rev: v0.0.272
- hooks:
- - id: ruff
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..6324d40
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.14
diff --git a/README.md b/README.md
index ef6c722..fca09b0 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,10 @@
[](https://pypi.org/project/iterum)
[](https://pypi.org/project/iterum)
-[](https://github.com/pypa/hatch)
+[](https://github.com/astral-sh/uv)
-[](https://github.com/astral-sh/ruff)
-[](https://github.com/psf/black)
+[](https://github.com/astral-sh/ruff)
[](https://microsoft.github.io/pyright/)
[](https://spdx.org/licenses/)
@@ -27,7 +26,7 @@ pip install iterum
## Documentation
-The [documentation](https://tjsmart.github.io/iterum) is made with [Material for MkDocs](https://github.com/squidfunk/mkdocs-material) and is hosted by [GitHub Pages](https://docs.github.com/en/pages).
+The [documentation](https://tjsmart.github.io/iterum) is made with [Zensical](https://zensical.org) and is hosted by [GitHub Pages](https://docs.github.com/en/pages).
## Example
diff --git a/docs/api.md b/docs/api.md
index 33b28f1..f516198 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1,7 +1,7 @@
# API
-*** Iterum ***
+**Iterum**
---
@@ -13,7 +13,7 @@
-*** Ranges ***
+**Ranges**
---
@@ -22,7 +22,7 @@
-*** Option ***
+**Option**
---
@@ -36,7 +36,7 @@
-*** Other ***
+**Other**
---
diff --git a/docs/index.md b/docs/index.md
index 1c02bed..8943751 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -6,11 +6,10 @@
[](https://pypi.org/project/iterum)
[](https://pypi.org/project/iterum)
-[](https://github.com/pypa/hatch)
+[](https://github.com/astral-sh/uv)
-[](https://github.com/astral-sh/ruff)
-[](https://github.com/psf/black)
+[](https://github.com/astral-sh/ruff)
[](https://microsoft.github.io/pyright/)
[](https://spdx.org/licenses/)
diff --git a/hatch.toml b/hatch.toml
deleted file mode 100644
index 5df5439..0000000
--- a/hatch.toml
+++ /dev/null
@@ -1,54 +0,0 @@
-[envs.default]
-dependencies = [
- "pytest",
- "pytest-cov",
-]
-[envs.default.scripts]
-cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=iterum --doctest-modules -vvvx {args}"
-no-cov = "cov --no-cov {args}"
-
-[[envs.test.matrix]]
-python = ["311"]
-
-[envs.lint]
-detached = true
-dependencies = [
- "pre-commit>=3.3.0",
- "pyright>=1.1.0",
-]
-
-[envs.lint.scripts]
-type = "pyright iterum type_tests"
-fmt = [
- "pre-commit run --all-files",
-]
-all = [
- "fmt",
- "type",
-]
-
-[envs.docs]
-dependencies = [
- "mkdocs~=1.4.3",
- "mkdocs-material~=9.1.17",
- # Plugins
- "mkdocs-minify-plugin~=0.5.0",
- "mkdocs-git-revision-date-localized-plugin~=1.1.0",
- "mkdocstrings-python~=1.1.2",
- "mkdocs-redirects~=1.1.0",
- "mkdocs-glightbox~=0.3.0",
- # https://github.com/jimporter/mike/issues/82#issuecomment-1172913929
- "mike @ https://github.com/jimporter/mike/archive/392d57b8bb9d14bcedf2451a0dc302709f8055eb.zip",
- # Extensions
- "mkdocs-click~=0.8.0",
- "pymdown-extensions~=10.0.1",
- # Necessary for syntax highlighting in code blocks
- "pygments~=2.15.1",
- # Validation
- # https://github.com/linkchecker/linkchecker/pull/669#issuecomment-1267236287
- "linkchecker @ git+https://github.com/linkchecker/linkchecker.git@d9265bb71c2054bf57b8c5734a4825d62505c779",
-]
-
-[envs.docs.scripts]
-build = "mkdocs build --clean --strict {args}"
-serve = "mkdocs serve {args}"
diff --git a/iterum/__about__.py b/iterum/__about__.py
deleted file mode 100644
index d3ec452..0000000
--- a/iterum/__about__.py
+++ /dev/null
@@ -1 +0,0 @@
-__version__ = "0.2.0"
diff --git a/iterum/__init__.py b/iterum/__init__.py
deleted file mode 100644
index 90a49e1..0000000
--- a/iterum/__init__.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from ._diterum import Diterum
-from ._diterum import diterum
-from ._diterum import Rev
-from ._iterum import Chain
-from ._iterum import Cycle
-from ._iterum import Enumerate
-from ._iterum import Filter
-from ._iterum import FilterMap
-from ._iterum import FlatMap
-from ._iterum import Flatten
-from ._iterum import Fuse
-from ._iterum import Inspect
-from ._iterum import Iterum
-from ._iterum import iterum
-from ._iterum import Map
-from ._iterum import MapWhile
-from ._iterum import Peekable
-from ._iterum import Scan
-from ._iterum import Skip
-from ._iterum import SkipWhile
-from ._iterum import State
-from ._iterum import StepBy
-from ._iterum import Take
-from ._iterum import TakeWhile
-from ._iterum import Zip
-from ._option import ExpectNilError
-from ._option import Nil
-from ._option import nil
-from ._option import Option
-from ._option import Some
-from ._option import Swap
-from ._option import UnwrapNilError
-from ._ordering import Ordering
-from ._seq import InfSeq
-from ._seq import Seq
-from ._seq import seq
-
-
-__all__ = [
- # Iterum
- "iterum",
- "Iterum",
- "diterum",
- "Diterum",
- # Sequential counter
- "seq",
- # Option
- "Option",
- "Some",
- "Nil",
- "nil",
- "ExpectNilError",
- "UnwrapNilError",
- # Ordering
- "Ordering",
- # special Iterum implementations
- "Chain",
- "Cycle",
- "Enumerate",
- "Filter",
- "FlatMap",
- "FilterMap",
- "Flatten",
- "Fuse",
- "Inspect",
- "Map",
- "MapWhile",
- "Peekable",
- "Scan",
- "Skip",
- "SkipWhile",
- "StepBy",
- "Take",
- "TakeWhile",
- "Zip",
- "InfSeq",
- # Special Diterum implementations
- "Rev",
- "Seq",
- # used by Scan
- "State",
- # used by swap operations in Option
- "Swap",
-]
diff --git a/iterum/_option.py b/iterum/_option.py
deleted file mode 100644
index 54429a5..0000000
--- a/iterum/_option.py
+++ /dev/null
@@ -1,1203 +0,0 @@
-from __future__ import annotations
-
-from collections.abc import Callable
-from typing import Any
-from typing import Generic
-from typing import Literal
-from typing import NamedTuple
-from typing import NoReturn
-from typing import overload
-from typing import TYPE_CHECKING
-from typing import TypeAlias
-from typing import TypeVar
-
-from ._singleton import Singleton
-
-if TYPE_CHECKING:
- from ._iterum import iterum
-
-
-T = TypeVar("T")
-U = TypeVar("U")
-V = TypeVar("V")
-
-S = TypeVar("S", bound="Some")
-O = TypeVar("O", bound="Option") # noqa: E741
-
-
-class Swap(NamedTuple, Generic[T, U]):
- """
- Used for various 'swapping' operations on [Option][iterum.Option].
- """
-
- inserted: T
- """
- Value inserted into an option
- """
-
- returned: U
- """
- Value returned from an option
- """
-
-
-class UnwrapNilError(RuntimeError):
- """
- Exception raised when [nil.unwrap][iterum.Nil.unwrap] is called.
- """
-
- def __init__(self, msg: str = "Attempted to unwrap nil") -> None:
- super().__init__(msg)
-
-
-class ExpectNilError(RuntimeError):
- """
- Exception raised when [nil.expect][iterum.Nil.expect] is called.
- """
-
- def __init__(self, msg: str = "Expected some but option is nil") -> None:
- super().__init__(msg)
-
-
-class Nil(Singleton):
- """
- [Nil][iterum.Nil] has no value.
-
- Examples:
-
- >>> x = Nil() # Type of "x" is "Nil"
- >>> x
- nil
- >>> x.is_nil()
- True
-
- [Nil][iterum.Nil] always returns the same object so just use [nil][iterum.nil] instead.
-
- >>> nil is Nil()
- True
-
- Likely, the only practical use of the [Nil][iterum.Nil] class is for type annotations and calls to `isinstance`.
- """
-
- __slots__ = ()
-
- def __repr__(self) -> str:
- return "nil"
-
- def __bool__(self) -> Literal[False]:
- return False
-
- def and_(self, optb: Option[U], /) -> Nil:
- """
- Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
- returns optb.
-
- Arguments passed to [and_][iterum.Nil.and_] are eagerly evaluated; if
- you are passing the result of a function call, it is recommended to use
- [and_then][iterum.Nil.and_then], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some(2).and_(nil) == nil
- >>> assert nil.and_(Some("foo")) == nil
- >>> assert Some(2).and_(Some("foo")) == Some("foo")
- >>> assert nil.and_(nil) == nil
-
- Note: because `and` is a keyword, this method is called `and_` instead.
- """
- # 'and' is a keyword, so instead we use 'and_'
- return self
-
- def and_then(self, f: Callable[[Any], Option[U]], /) -> Nil:
- """
- Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
- calls `f` with the wrapped value and returns the result.
-
- Examples:
-
- >>> MAX_U32 = (1 << 32) - 1
- >>> def checked_sq_u32(x: int) -> Option[int]:
- ... sq = x * x
- ... if sq > MAX_U32:
- ... return nil
- ... return Some(sq)
- ...
- >>> assert Some(2).and_then(checked_sq_u32) == Some(4)
- >>> assert Some(1_000_000).and_then(checked_sq_u32) == nil
- >>> assert nil.and_then(checked_sq_u32) == nil
- """
- return self
-
- def expect(self, msg: str, /) -> NoReturn:
- """
- Returns the contained [Some][iterum.Some] value, consuming the self value.
-
- Examples:
-
- >>> x = Some("value")
- >>> assert x.expect("fruits are healthy") == "value"
-
- >>> try:
- ... nil.expect("fruits are healthy")
- ... except ExpectNilError as ex:
- ... print(ex)
- ...
- fruits are healthy
-
- Raises:
- ExpectNilError: if the value is a [nil][iterum.nil] with a custom
- panic message provided by msg.
- """
- raise ExpectNilError(msg)
-
- def filter(self, predicate: Callable[[Any], object], /) -> Nil:
- """
- Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
- calls `predicate` with the wrapped value and returns:
-
- - [Some(value)][iterum.Some] if the predicate returns `True`
- - [nil][iterum.nil] if the predicate returns `False`
-
- Examples:
-
- >>> assert nil.filter(lambda x: x % 2 == 0) == nil
- >>> assert Some(3).filter(lambda x: x % 2 == 0) == nil
- >>> assert Some(4).filter(lambda x: x % 2 == 0) == Some(4)
- """
- return self
-
- def flatten(self) -> Nil:
- """
- Converts from `Option[Option[T]]` to `Option[T]`.
-
- Examples:
-
- >>> assert Some(Some(6)).flatten() == Some(6)
- >>> assert Some(nil).flatten() == nil
- >>> assert nil.flatten() == nil
- """
- return self
-
- def get_or_insert(self, value: T, /) -> Swap[Some[T], T]:
- """
- Inserts value into the option if it is [nil][iterum.nil], then returns a
- tuple of the resulting option and the returned value.
-
- See also [insert][iterum.Nil.insert], which updates the value even if
- the option already contains a value.
-
- Examples:
-
- >>> opt = nil
- >>> opt, value = opt.get_or_insert(5)
- >>> assert value == 5
- >>> assert opt == Some(5)
-
- >>> opt = Some(3)
- >>> opt, value = opt.get_or_insert(5)
- >>> assert value == 3
- >>> assert opt == Some(3)
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> assert Some(10).get_or_insert(5).returned == 10
- >>> assert nil.get_or_insert(5).returned == 5
-
- >>> assert Some(10).get_or_insert(5).inserted == Some(10)
- >>> assert nil.get_or_insert(5).inserted == Some(5)
- """
- return Swap(Some(value), value)
-
- def get_or_insert_with(self, f: Callable[[], T], /) -> Swap[Some[T], T]:
- """
- Inserts a value computed from `f` into the option if it is
- [nil][iterum.nil], then returns a tuple of the resulting option and the
- returned value.
-
- Examples:
-
- >>> opt = nil
- >>> opt, value = opt.get_or_insert_with(lambda: 5)
- >>> assert value == 5
- >>> assert opt == Some(5)
-
- >>> opt = Some(3)
- >>> opt, value = opt.get_or_insert_with(lambda: 5)
- >>> assert value == 3
- >>> assert opt == Some(3)
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(10).get_or_insert_with(lambda: 5)
- >>> assert swap.inserted == Some(10)
- >>> assert swap.returned == 10
-
- >>> swap = nil.get_or_insert_with(lambda: 5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == 5
- """
- return Swap(Some(value := f()), value)
-
- def insert(self, value: T, /) -> Swap[Some[T], T]:
- """
- Inserts value into the option, then returns a tuple of the resulting
- option and the returned value.
-
- If the option already contains a value, the old value is dropped.
-
- See also [get_or_insert][iterum.Nil.get_or_insert], which doesn’t
- update the value if the option already contains a value.
-
- Examples:
-
- >>> opt = nil
- >>> opt, value = opt.insert(1)
- >>> assert value == 1
- >>> assert opt == Some(1)
-
- >>> opt = Some(3)
- >>> opt, value = opt.insert(1)
- >>> assert value == 1
- >>> assert opt == Some(1)
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(10).insert(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == 5
-
- >>> swap = nil.insert(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == 5
- """
- return Swap(Some(value), value)
-
- def is_nil(self) -> Literal[True]:
- """
- Returns `True` if the option is a [nil][iterum.nil] value.
-
- Examples:
-
- >>> assert Some(2).is_nil() is False
- >>> assert nil.is_nil() is True
- """
- return True
-
- def is_some(self) -> Literal[False]:
- """
- Returns `True` if the option is a Some value.
-
- Examples:
-
- >>> assert Some(2).is_some() is True
- >>> assert nil.is_some() is False
- """
- return False
-
- def is_some_and(self, f: Callable[[Any], object]) -> Literal[False]:
- """
- Returns `True` if the option is a [Some][iterum.Some] and the value
- inside of it matches a predicate.
-
- Examples:
-
- >>> assert Some(2).is_some_and(lambda x: x > 1) is True
- >>> assert Some(0).is_some_and(lambda x: x > 1) is False
- >>> assert nil.is_some_and(lambda x: x > 1) is False
- """
- return False
-
- def iter(self) -> iterum[Any]:
- """
- Returns an iterator over the possibly contained value.
-
- Examples:
-
- >>> assert Some(4).iter().next() == Some(4)
- >>> assert nil.iter().next() == nil
- """
- from ._iterum import iterum
-
- return iterum([])
-
- def map(self, f: Callable[[Any], Any], /) -> Nil:
- """
- Maps an [Option[T]][iterum.Option] to [Option[U]][iterum.Option] by
- applying a function to a contained value (if [Some][iterum.Some]) or
- returns [nil][iterum.nil] (if [Nil][iterum.Nil]).
-
- Examples:
-
- >>> assert Some("Hello, World!").map(len) == Some(13)
- >>> assert nil.map(len) == nil
- """
- return self
-
- def map_or(self, default: U, f: Callable[[Any], U], /) -> U:
- """
- Returns the provided default result (if [nil][iterum.nil]), or applies a
- function to the contained value (if any).
-
- Arguments passed to [map_or][iterum.Nil.map_or] are eagerly evaluated;
- if you are passing the result of a function call, it is recommended to
- use [map_or_else][iterum.Nil.map_or_else], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some("foo").map_or(42, len) == 3
- >>> assert nil.map_or(42, len) == 42
- """
- return default
-
- def map_or_else(self, default: Callable[[], U], f: Callable[[Any], U], /) -> U:
- """
- Computes a default function result (if [nil][iterum.nil]), or applies a
- different function to the contained value (if any).
-
- Examples:
-
- >>> k = 21
- >>> assert Some("foo").map_or_else(lambda: 2 * k, len) == 3
- >>> assert nil.map_or_else(lambda: 2 * k, len) == 42
- """
- return default()
-
- def ok_or(self, err: Exception, /) -> NoReturn:
- """
- Unwraps the option returning the value if [Some][iterum.Some] or raises
- the provided exception if [nil][iterum.nil].
-
- Arguments passed to [ok_or][iterum.Nil.ok_or] are eagerly evaluated; if
- you are passing the result of a function call, it is recommended to use
- [ok_or_else][iterum.Nil.ok_or_else], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some("foo").ok_or(RuntimeError("oh no!")) == "foo"
-
- >>> try:
- ... nil.ok_or(RuntimeError("oh no!"))
- ... except RuntimeError as ex:
- ... print(ex)
- ...
- oh no!
- """
- raise err
-
- def ok_or_else(self, err: Callable[[], Exception], /) -> NoReturn:
- """
- Unwraps the option returning the value if [Some][iterum.Some] or raises
- the exception returned by the provided callable if [nil][iterum.nil].
-
- Examples:
-
- >>> assert Some("foo").ok_or_else(AssertionError) == "foo"
-
- >>> try:
- ... nil.ok_or_else(lambda: AssertionError("oopsy!"))
- ... except AssertionError as ex:
- ... print(ex)
- ...
- oopsy!
- """
- raise err()
-
- def or_(self, optb: O, /) -> O:
- """
- Returns the option if it contains a value, otherwise returns optb.
-
- Arguments passed to [or_][iterum.Nil.or_] are eagerly evaluated; if you
- are passing the result of a function call, it is recommended to use
- [or_else][iterum.Nil.or_else], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some(2).or_(nil) == Some(2)
- >>> assert nil.or_(Some(100)) == Some(100)
- >>> assert Some(2).or_(Some(100)) == Some(2)
- >>> assert nil.or_(nil) == nil
-
- Note: because `or` is a keyword, this method is called `or_` instead.
- """
- # 'or' is a keyword, so instead we use 'or_'
- return optb
-
- def or_else(self, f: Callable[[], O], /) -> O:
- """
- Returns the option if it contains a value, otherwise calls `f` and
- returns the result.
-
- Examples:
-
- >>> def nobody() -> Option[str]:
- ... return nil
- ...
- >>> def vikings() -> Option[str]:
- ... return Some("vikings")
- ...
- >>> assert Some("barbarians").or_else(vikings) == Some("barbarians")
- >>> assert nil.or_else(vikings) == Some("vikings")
- >>> assert nil.or_else(nobody) == nil
- """
- return f()
-
- def replace(self, value: T, /) -> Swap[Some[T], Nil]:
- """
- Replaces the actual value in the option by the value given in parameter,
- returning a tuple of the resulting option and the returned old value if
- present.
-
- Examples:
-
- >>> x = Some(2)
- >>> new, old = x.replace(5)
- >>> assert new == Some(5)
- >>> assert old == Some(2)
-
- >>> x = nil
- >>> new, old = x.replace(5)
- >>> assert new == Some(5)
- >>> assert old == nil
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(10).replace(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == Some(10)
-
- >>> swap = nil.replace(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == nil
- """
- return Swap(Some(value), nil)
-
- def take(self) -> Swap[Nil, Nil]:
- """
- Takes the value out of the option, returning a tuple of the resulting
- nil and the old option.
-
- Examples:
-
- >>> x = Some(2)
- >>> new, old = x.take()
- >>> assert new == nil
- >>> assert old == Some(2)
-
- >>> x = nil
- >>> new, old = x.take()
- >>> assert new == nil
- >>> assert old == nil
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(2).take()
- >>> assert swap.inserted == nil
- >>> assert swap.returned == Some(2)
-
- >>> swap = nil.take()
- >>> assert swap.inserted == nil
- >>> assert swap.returned == nil
- """
- return Swap(nil, self)
-
- # transpose ... without a Result concept there isn't any value
-
- def unwrap(self) -> NoReturn:
- """
- Returns the contained [Some][iterum.Some] value.
-
- Examples:
-
- >>> assert Some("air").unwrap() == "air"
-
- >>> try:
- ... nil.unwrap()
- ... except UnwrapNilError as ex:
- ... print("Attempted to unwrap a nil!")
- ...
- Attempted to unwrap a nil!
-
- Raises:
- UnwrapNilError: if the value is a [nil][iterum.nil].
- """
- raise UnwrapNilError()
-
- def unwrap_or(self, default: T, /) -> T:
- """
- Returns the contained [Some][iterum.Some] value or a provided default.
-
- Arguments passed to [unwrap_or][iterum.Nil.unwrap_or] are eagerly
- evaluated; if you are passing the result of a function call, it is
- recommended to use [unwrap_or_else][iterum.Nil.unwrap_or_else], which
- is lazily evaluated.
-
- Examples:
-
- >>> assert Some("car").unwrap_or("bike") == "car"
- >>> assert nil.unwrap_or("bike") == "bike"
- """
- return default
-
- # In order for unwrap_or_default to be implemented we would
- # need to know within nil what type we are supposed to have.
- #
- # If this was known we could come up with reasonable defaults, e.g. 0, {}, [], "", ...
- # note: these also happen to be what constructing the type with no params gives.
- #
- # If I wanted to get real fancy could provide user way to register defaults
- # for their custom types.
-
- def unwrap_or_else(self, f: Callable[[], T], /) -> T:
- """
- Returns the contained [Some][iterum.Some] value or computes it from a closure.
-
- Examples:
-
- >>> k = 10
- >>> assert Some(4).unwrap_or_else(lambda: 2 * k) == 4
- >>> assert nil.unwrap_or_else(lambda: 2 * k) == 20
- """
- return f()
-
- def unzip(self) -> tuple[Nil, Nil]:
- """
- Unzips an option containing a tuple of two options.
-
- If `self` is `Some((a, b))` this method returns `(Some(a), Some(b))`.
- Otherwise, `(nil, nil)` is returned.
-
- Examples:
-
- >>> assert Some((1, "hi")).unzip() == (Some(1), Some("hi"))
- >>> assert nil.unzip() == (nil, nil)
- """
- return (nil, nil)
-
- @overload
- def xor(self, optb: S, /) -> S:
- ...
-
- @overload
- def xor(self, optb: Nil, /) -> Nil:
- ...
-
- def xor(self, optb: O, /) -> O | Nil:
- """
- Returns [Some][iterum.Some] if exactly one of `self`, `optb` is
- [Some][iterum.Some], otherwise returns [nil][iterum.nil].
-
- Examples:
-
- >>> assert Some(2).xor(nil) == Some(2)
- >>> assert nil.xor(Some(100)) == Some(100)
- >>> assert Some(2).xor(Some(100)) == nil
- >>> assert nil.xor(nil) == nil
- """
- return nil if isinstance(optb, Nil) else optb
-
- def zip(self, other: Option[U], /) -> Nil:
- """
- Zips `self` with another option.
-
- If `self` is `Some(s)` and `other` is `Some(o)`,
- this method returns `Some((s, o))`.
- Otherwise, [nil][iterum.nil] is returned.
-
- Examples:
-
- >>> assert Some(1).zip(Some("hi")) == Some((1, "hi"))
- >>> assert Some(1).zip(nil) == nil
- >>> assert nil.zip(nil) == nil
- """
- return self
-
-
-nil = Nil()
-"""
-Instance of type [Nil][iterum.Nil]. See [Nil][iterum.Nil] for more details.
-"""
-
-
-class Some(Generic[T]):
- """
- [Some][iterum.Some] value of type T.
-
- Examples:
-
- >>> x = Some(1) # Type of "x" is "Some[int]"
- >>> x
- Some(1)
- >>> x.is_some()
- True
- >>> x.unwrap()
- 1
- """
-
- __match_args__ = ("_value",)
-
- def __init__(self, value: T, /) -> None:
- self._value = value
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, Some):
- return NotImplemented
- return self._value == other._value
-
- def __repr__(self) -> str:
- return f"{Some.__name__}({self._value!r})"
-
- def __bool__(self) -> Literal[True]:
- return True
-
- def and_(self, optb: O, /) -> O:
- """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
- returns optb.
-
- Arguments passed to [and_][iterum.Some.and_] are eagerly evaluated; if
- you are passing the result of a function call, it is recommended to use
- [and_then][iterum.Some.and_then], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some(2).and_(nil) == nil
- >>> assert nil.and_(Some("foo")) == nil
- >>> assert Some(2).and_(Some("foo")) == Some("foo")
- >>> assert nil.and_(nil) == nil
-
- Note: because `and` is a keyword, this method is called `and_` instead.
- """
- return optb
-
- def and_then(self, f: Callable[[T], O], /) -> O:
- """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
- calls `f` with the wrapped value and returns the result.
-
- Examples:
-
-
- >>> MAX_U32 = (1 << 32) - 1
- >>> def checked_sq_u32(x: int) -> Option[int]:
- ... sq = x * x
- ... if sq > MAX_U32:
- ... return nil
- ... return Some(sq)
- ...
- >>> assert Some(2).and_then(checked_sq_u32) == Some(4)
- >>> assert Some(1_000_000).and_then(checked_sq_u32) == nil
- >>> assert nil.and_then(checked_sq_u32) == nil
- """
- return f(self._value)
-
- def expect(self, msg: str, /) -> T:
- """Returns the contained [Some][iterum.Some] value, consuming the self value.
-
- Examples:
-
- >>> x = Some("value")
- >>> assert x.expect("fruits are healthy") == "value"
-
- >>> try:
- ... nil.expect("fruits are healthy")
- ... except ExpectNilError as ex:
- ... print(ex)
- ...
- fruits are healthy
-
- Raises:
- ExpectNilError: if the value is a [nil][iterum.nil] with a custom
- message provided by msg.
- """
- return self._value
-
- def filter(self, predicate: Callable[[T], object], /) -> Option[T]:
- """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
- calls `predicate` with the wrapped value and returns:
-
- - [Some(value)][iterum.Some] if the predicate returns `True`
- - [nil][iterum.nil] if the predicate returns `False`
-
- Examples:
-
- >>> assert nil.filter(lambda x: x % 2 == 0) == nil
- >>> assert Some(3).filter(lambda x: x % 2 == 0) == nil
- >>> assert Some(4).filter(lambda x: x % 2 == 0) == Some(4)
- """
- return self if predicate(self._value) else Nil()
-
- def flatten(self: Some[O]) -> O:
- """Converts from `Option[Option[T]]` to `Option[T]`.
-
- Examples:
-
- >>> assert Some(Some(6)).flatten() == Some(6)
- >>> assert Some(nil).flatten() == nil
- >>> assert nil.flatten() == nil
- """
- if isinstance(self._value, (Some, Nil)):
- return self._value
- else:
- raise TypeError(f"Cannot flatten type: Some({type(self._value).__name__})")
-
- def get_or_insert(self, value: T, /) -> Swap[Some[T], T]:
- """Inserts value into the option if it is [nil][iterum.nil], then returns a
- tuple of the resulting option and the returned value.
-
- See also [insert][iterum.Some.insert], which updates the value even if
- the option already contains a value.
-
- Examples:
-
- >>> opt = nil
- >>> opt, value = opt.get_or_insert(5)
- >>> assert value == 5
- >>> assert opt == Some(5)
-
- >>> opt = Some(3)
- >>> opt, value = opt.get_or_insert(5)
- >>> assert value == 3
- >>> assert opt == Some(3)
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> assert Some(10).get_or_insert(5).returned == 10
- >>> assert nil.get_or_insert(5).returned == 5
-
- >>> assert Some(10).get_or_insert(5).inserted == Some(10)
- >>> assert nil.get_or_insert(5).inserted == Some(5)
- """
- return Swap(Some(self._value), self._value)
-
- def get_or_insert_with(self, f: Callable[[], T], /) -> Swap[Some[T], T]:
- """Inserts a value computed from `f` into the option if it is
- [nil][iterum.nil], then returns a tuple of the resulting option and the
- returned value.
-
- Examples:
-
- >>> opt = nil
- >>> opt, value = opt.get_or_insert_with(lambda: 5)
- >>> assert value == 5
- >>> assert opt == Some(5)
-
- >>> opt = Some(3)
- >>> opt, value = opt.get_or_insert_with(lambda: 5)
- >>> assert value == 3
- >>> assert opt == Some(3)
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(10).get_or_insert_with(lambda: 5)
- >>> assert swap.inserted == Some(10)
- >>> assert swap.returned == 10
-
- >>> swap = nil.get_or_insert_with(lambda: 5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == 5
- """
- return Swap(Some(self._value), self._value)
-
- def insert(self, value: T, /) -> Swap[Some[T], T]:
- """Inserts value into the option, then returns a tuple of the resulting
- option and the returned value.
-
- If the option already contains a value, the old value is dropped.
-
- See also [get_or_insert][iterum.Some.get_or_insert], which doesn’t
- update the value if the option already contains a value.
-
- Examples:
-
- >>> opt = nil
- >>> opt, value = opt.insert(1)
- >>> assert value == 1
- >>> assert opt == Some(1)
-
- >>> opt = Some(3)
- >>> opt, value = opt.insert(1)
- >>> assert value == 1
- >>> assert opt == Some(1)
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(10).insert(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == 5
-
- >>> swap = nil.insert(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == 5
- """
- self._value = value
- return Swap(Some(self._value), self._value)
-
- def is_nil(self) -> Literal[False]:
- """Returns `True` if the option is a [nil][iterum.nil] value.
-
- Examples:
-
- >>> assert Some(2).is_nil() is False
- >>> assert nil.is_nil() is True
- """
- return False
-
- def is_some(self) -> Literal[True]:
- """Returns `True` if the option is a Some value.
-
- Examples:
-
- >>> assert Some(2).is_some() is True
- >>> assert nil.is_some() is False
- """
- return True
-
- def is_some_and(self, f: Callable[[T], object]) -> bool:
- """Returns `True` if the option is a [Some][iterum.Some] and the value
- inside of it matches a predicate.
-
- Examples:
-
- >>> assert Some(2).is_some_and(lambda x: x > 1) is True
- >>> assert Some(0).is_some_and(lambda x: x > 1) is False
- >>> assert nil.is_some_and(lambda x: x > 1) is False
- """
- return bool(f(self.unwrap()))
-
- def iter(self) -> iterum[T]:
- """Returns an iterator over the possibly contained value.
-
- Examples:
-
- >>> assert Some(4).iter().next() == Some(4)
- >>> assert nil.iter().next() == nil
- """
- from ._iterum import iterum
-
- return iterum([self._value])
-
- def map(self, f: Callable[[T], U], /) -> Some[U]:
- """Maps an [Option[T]][iterum.Option] to [Option[U]][iterum.Option] by
- applying a function to a contained value (if [Some][iterum.Some]) or
- returns [nil][iterum.nil] (if [Nil][iterum.Nil]).
-
- Examples:
-
- >>> assert Some("Hello, World!").map(len) == Some(13)
- >>> assert nil.map(len) == nil
- """
- return Some(f(self._value))
-
- def map_or(self, default: U, f: Callable[[T], U], /) -> U:
- """
- Returns the provided default result (if [nil][iterum.nil]), or applies a
- function to the contained value (if any).
-
- Arguments passed to [map_or][iterum.Some.map_or] are eagerly evaluated;
- if you are passing the result of a function call, it is recommended to
- use [map_or_else][iterum.Some.map_or_else], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some("foo").map_or(42, len) == 3
- >>> assert nil.map_or(42, len) == 42
- """
- return f(self._value)
-
- def map_or_else(self, default: Callable[[], U], f: Callable[[T], U], /) -> U:
- """
- Computes a default function result (if [nil][iterum.nil]), or applies a
- different function to the contained value (if any).
-
- Examples:
-
- >>> k = 21
- >>> assert Some("foo").map_or_else(lambda: 2 * k, len) == 3
- >>> assert nil.map_or_else(lambda: 2 * k, len) == 42
- """
- return f(self._value)
-
- def ok_or(self, err: Exception, /) -> T:
- """Unwraps the option returning the value if [Some][iterum.Some] or raises
- the provided exception if [nil][iterum.nil].
-
- Arguments passed to [ok_or][iterum.Some.ok_or] are eagerly evaluated; if
- you are passing the result of a function call, it is recommended to use
- [ok_or_else][iterum.Some.ok_or_else], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some("foo").ok_or(RuntimeError("oh no!")) == "foo"
-
- >>> try:
- ... nil.ok_or(RuntimeError("oh no!"))
- ... except RuntimeError as ex:
- ... print(ex)
- ...
- oh no!
- """
- return self._value
-
- def ok_or_else(self, err: Callable[[], Exception], /) -> T:
- """Unwraps the option returning the value if [Some][iterum.Some] or raises
- the exception returned by the provided callable if [nil][iterum.nil].
-
- Examples:
-
- >>> assert Some("foo").ok_or_else(AssertionError) == "foo"
-
- >>> try:
- ... nil.ok_or_else(lambda: AssertionError("oopsy!"))
- ... except AssertionError as ex:
- ... print(ex)
- ...
- oopsy!
- """
- return self._value
-
- def or_(self, optb: Option[T], /) -> Some[T]:
- """Returns the option if it contains a value, otherwise returns optb.
-
- Arguments passed to [or_][iterum.Some.or_] are eagerly evaluated; if you
- are passing the result of a function call, it is recommended to use
- [or_else][iterum.Some.or_else], which is lazily evaluated.
-
- Examples:
-
- >>> assert Some(2).or_(nil) == Some(2)
- >>> assert nil.or_(Some(100)) == Some(100)
- >>> assert Some(2).or_(Some(100)) == Some(2)
- >>> assert nil.or_(nil) == nil
-
- Note: because `or` is a keyword, this method is called `or_` instead.
- """
- # 'or' is a keyword, so instead we use 'or_'
- return self
-
- def or_else(self, f: Callable[[], Option[T]], /) -> Some[T]:
- """Returns the option if it contains a value, otherwise calls `f` and
- returns the result.
-
- Examples:
-
- >>> def nobody() -> Option[str]:
- ... return nil
- ...
- >>> def vikings() -> Option[str]:
- ... return Some("vikings")
- ...
- >>> assert Some("barbarians").or_else(vikings) == Some("barbarians")
- >>> assert nil.or_else(vikings) == Some("vikings")
- >>> assert nil.or_else(nobody) == nil
- """
- return self
-
- def replace(self, value: T, /) -> Swap[Some[T], Some[T]]:
- """Replaces the actual value in the option by the value given in parameter,
- returning a tuple of the resulting option and the returned old value if
- present.
-
- Examples:
-
- >>> x = Some(2)
- >>> new, old = x.replace(5)
- >>> assert new == Some(5)
- >>> assert old == Some(2)
-
- >>> x = nil
- >>> new, old = x.replace(5)
- >>> assert new == Some(5)
- >>> assert old == nil
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(10).replace(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == Some(10)
-
- >>> swap = nil.replace(5)
- >>> assert swap.inserted == Some(5)
- >>> assert swap.returned == nil
- """
- old = self._value
- self._value = value
- return Swap(Some(self._value), Some(old))
-
- def take(self) -> Swap[Nil, Some[T]]:
- """Takes the value out of the option, returning a tuple of the resulting
- nil and the old option.
-
- Examples:
-
- >>> x = Some(2)
- >>> new, old = x.take()
- >>> assert new == nil
- >>> assert old == Some(2)
-
- >>> x = nil
- >>> new, old = x.take()
- >>> assert new == nil
- >>> assert old == nil
-
- Alternatively, access the named attributes of [Swap][iterum.Swap],
- [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
-
- >>> swap = Some(2).take()
- >>> assert swap.inserted == nil
- >>> assert swap.returned == Some(2)
-
- >>> swap = nil.take()
- >>> assert swap.inserted == nil
- >>> assert swap.returned == nil
- """
- return Swap(nil, self)
-
- # transpose ... without a Result concept there isn't any value
-
- def unwrap(self) -> T:
- """Returns the contained [Some][iterum.Some] value.
-
- Examples:
-
- >>> assert Some("air").unwrap() == "air"
-
- >>> try:
- ... nil.unwrap()
- ... except UnwrapNilError as ex:
- ... print("Attempted to unwrap a nil!")
- ...
- Attempted to unwrap a nil!
-
- Raises:
- UnwrapNilError: if the value is a [nil][iterum.nil].
- """
- return self._value
-
- def unwrap_or(self, default: T, /) -> T:
- """
- Returns the contained [Some][iterum.Some] value or a provided default.
-
- Arguments passed to [unwrap_or][iterum.Some.unwrap_or] are eagerly
- evaluated; if you are passing the result of a function call, it is
- recommended to use [unwrap_or_else][iterum.Some.unwrap_or_else], which
- is lazily evaluated.
-
- Examples:
-
- >>> assert Some("car").unwrap_or("bike") == "car"
- >>> assert nil.unwrap_or("bike") == "bike"
- """
- return self._value
-
- def unwrap_or_else(self, f: Callable[[], T], /) -> T:
- """Returns the contained [Some][iterum.Some] value or computes it from a closure.
-
- Examples:
-
- >>> k = 10
- >>> assert Some(4).unwrap_or_else(lambda: 2 * k) == 4
- >>> assert nil.unwrap_or_else(lambda: 2 * k) == 20
- """
- return self._value
-
- def unzip(self: Some[tuple[U, V]]) -> tuple[Some[U], Some[V]]:
- """Unzips an option containing a tuple of two options.
-
- If `self` is `Some((a, b))` this method returns `(Some(a), Some(b))`.
- Otherwise, `(nil, nil)` is returned.
-
- Examples:
-
- >>> assert Some((1, "hi")).unzip() == (Some(1), Some("hi"))
- >>> assert nil.unzip() == (nil, nil)
- """
- left, right = self._value
- return Some(left), Some(right)
-
- @overload
- def xor(self, optb: Some[T], /) -> Nil:
- ...
-
- @overload
- def xor(self, optb: Nil, /) -> Some[T]:
- ...
-
- def xor(self, optb: Option[T], /) -> Option[T]:
- """
- Returns [Some][iterum.Some] if exactly one of `self`, `optb` is
- [Some][iterum.Some], otherwise returns [nil][iterum.nil].
-
- Examples:
-
- >>> assert Some(2).xor(nil) == Some(2)
- >>> assert nil.xor(Some(100)) == Some(100)
- >>> assert Some(2).xor(Some(100)) == nil
- >>> assert nil.xor(nil) == nil
- """
- return self if isinstance(optb, Nil) else nil
-
- @overload
- def zip(self, other: Some[U], /) -> Some[tuple[T, U]]:
- ...
-
- @overload
- def zip(self, other: Nil, /) -> Nil:
- ...
-
- def zip(self, other: Option[U], /) -> Option[tuple[T, U]]:
- """
- Zips `self` with another option.
-
- If `self` is `Some(s)` and `other` is `Some(o)`,
- this method returns `Some((s, o))`.
- Otherwise, [nil][iterum.nil] is returned.
-
- Examples:
-
- >>> assert Some(1).zip(Some("hi")) == Some((1, "hi"))
- >>> assert Some(1).zip(nil) == nil
- >>> assert nil.zip(nil) == nil
- """
- return nil if isinstance(other, Nil) else Some((self._value, other._value))
-
-
-Option: TypeAlias = "Some[T] | Nil"
-"""
-Type alias representing something which is either of type
-[Some][iterum.Some] or [Nil][iterum.Nil].
-
-Examples:
-
- Type annotate a function which returns `Some[int]` or `nil`:
- >>> def checked_div(num: int, dem: int) -> Option[int]:
- ... try:
- ... return Some(num // dem)
- ... except ZeroDivisionError:
- ... return nil
- ...
-
- Use `isinstance` to narrow the type:
- >>> x = checked_div(10, 3)
- >>> reveal_type(x) # Type of "x" is "Some[int] | Nil"
- >>> if isinstance(x, Some):
- ... reveal_type(x) # Type of "x" is "Some[int]"
- ... else:
- ... reveal_type(x) # Type of "x" is "Nil"
- ...
-
- Alternatively use pattern matching:
- >>> match x:
- ... case Some(value):
- ... print(f"Result: {value=}")
- ... case Nil:
- ... print("Cannot divide by 0")
- ...
-}
-"""
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 70bead0..0000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,77 +0,0 @@
-site_name: iterum
-site_author: tjsmart
-
-repo_url: https://github.com/tjsmart/iterum
-repo_name: tjsmart/iterum
-
-theme:
- name: material
- features:
- - navigation.tabs
- - navigation.top
- - navigation.footer
- - navigation.sections
- - toc.integrate
- - search.suggest
- - search.highlight
- - content.tabs.link
- - content.code.annotation
- # - content.code.copy
- language: en
- logo: assets/logo.png
- favicon: assets/logo.png
- palette:
- scheme: slate
- primary: red
- accent: red
-
-extra:
- social:
- - icon: fontawesome/brands/github-alt
- link: https://github.com/tjsmart
-
-markdown_extensions:
- - pymdownx.highlight:
- anchor_linenums: true
- - pymdownx.inlinehilite
- - pymdownx.snippets
- - admonition
- - pymdownx.arithmatex:
- generic: true
- - footnotes
- - pymdownx.details
- - pymdownx.superfences
- - pymdownx.mark
- - attr_list
-
-copyright: |
- © 2023 Tyler Smart
-
-nav:
- - Overview: 'index.md'
- - API: 'api.md'
-
-plugins:
- - mkdocstrings:
- default_handler: python
- handlers:
- python:
- paths:
- - src
- options:
- # Headings
- show_root_heading: true
- show_root_full_path: true
- # Docstrings
- show_if_no_docstring: false
- # Signatures/annotations
- show_signature_annotations: true
- # Member options
- members_order: source
- # Other
- show_bases: true
- show_source: true
- - search
-
-extra_css:
- - stylesheets/extra.css
diff --git a/pyproject.toml b/pyproject.toml
index 14421e9..91cfa7f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,12 @@
[build-system]
-requires = ["hatchling"]
-build-backend = "hatchling.build"
+requires = ["uv_build"]
+build-backend = "uv_build"
[project]
name = "iterum"
description = 'Rusty iterators in Python.'
readme = "README.md"
-requires-python = ">=3.11"
+requires-python = ">=3.14"
license = "MIT"
keywords = []
authors = [
@@ -15,34 +15,24 @@ authors = [
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
- "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
]
-dynamic = ["version"]
+version = "0.2.0"
-[project.urls]
-Documentation = "https://tjsmart.github.io/iterum"
-Source = "https://github.com/tjsmart/iterum"
-
-[tool.setuptools.package-data]
-iterum = ["py.typed"]
-
-[tool.hatch]
-version.path = "iterum/__about__.py"
-
-[tool.coverage.run]
-branch = true
-parallel = true
-omit = [
- "iterum/__about__.py",
+[dependency-groups]
+dev = [
+ "pyright>=1.1.409",
+ "pytest",
+ "pytest-cov",
+ "ruff>=0.8.0",
]
-[tool.coverage.report]
-exclude_lines = [
- "no cov",
- "if __name__ == .__main__.:",
- "if TYPE_CHECKING:",
+docs = [
+ "zensical",
+ "mkdocstrings-python",
]
-[tool.ruff]
-line-length = 120
+[tool.ruff.lint]
+select = ["E", "F", "I", "B", "UP", "RUF"]
+ignore = ["RUF022"]
diff --git a/src/iterum/__init__.py b/src/iterum/__init__.py
new file mode 100644
index 0000000..345ba0c
--- /dev/null
+++ b/src/iterum/__init__.py
@@ -0,0 +1,75 @@
+from ._diterum import Diterum, Rev, diterum
+from ._iterum import (
+ Chain,
+ Cycle,
+ Enumerate,
+ Filter,
+ FilterMap,
+ FlatMap,
+ Flatten,
+ Fuse,
+ Inspect,
+ Iterum,
+ Map,
+ MapWhile,
+ Peekable,
+ Scan,
+ Skip,
+ SkipWhile,
+ State,
+ StepBy,
+ Take,
+ TakeWhile,
+ Zip,
+ iterum,
+)
+from ._option import ExpectNilError, Nil, Option, Some, Swap, UnwrapNilError, nil
+from ._ordering import Ordering
+from ._seq import InfSeq, Seq, seq
+
+__all__ = [
+ # Iterum
+ "iterum",
+ "Iterum",
+ "diterum",
+ "Diterum",
+ # Sequential counter
+ "seq",
+ # Option
+ "Option",
+ "Some",
+ "Nil",
+ "nil",
+ "ExpectNilError",
+ "UnwrapNilError",
+ # Ordering
+ "Ordering",
+ # special Iterum implementations
+ "Chain",
+ "Cycle",
+ "Enumerate",
+ "Filter",
+ "FlatMap",
+ "FilterMap",
+ "Flatten",
+ "Fuse",
+ "Inspect",
+ "Map",
+ "MapWhile",
+ "Peekable",
+ "Scan",
+ "Skip",
+ "SkipWhile",
+ "StepBy",
+ "Take",
+ "TakeWhile",
+ "Zip",
+ "InfSeq",
+ # Special Diterum implementations
+ "Rev",
+ "Seq",
+ # used by Scan
+ "State",
+ # used by swap operations in Option
+ "Swap",
+]
diff --git a/iterum/_diterum.py b/src/iterum/_diterum.py
similarity index 61%
rename from iterum/_diterum.py
rename to src/iterum/_diterum.py
index 1f96819..6008948 100644
--- a/iterum/_diterum.py
+++ b/src/iterum/_diterum.py
@@ -1,15 +1,10 @@
from __future__ import annotations
from abc import abstractmethod
-from collections.abc import Callable
-from collections.abc import Sequence
+from collections.abc import Callable, Sequence
-from ._iterum import Iterum
-from ._iterum import T_co
-from ._iterum import U
-from ._option import nil
-from ._option import Option
-from ._option import Some
+from ._iterum import Iterum, T_co, U
+from ._option import Option, Some, nil
class Diterum(Iterum[T_co]):
@@ -32,17 +27,20 @@ def next_back(self) -> Option[T_co]:
Returns [nil][iterum.nil] when there are no more elements.
- Examples:
-
- >>> di = diterum([1, 2, 3, 4, 5, 6])
- >>> assert di.next() == Some(1)
- >>> assert di.next_back() == Some(6)
- >>> assert di.next_back() == Some(5)
- >>> assert di.next() == Some(2)
- >>> assert di.next() == Some(3)
- >>> assert di.next() == Some(4)
- >>> assert di.next() == nil
- >>> assert di.next_back() == nil
+ **Examples:**
+
+ ```python
+ >>> di = diterum([1, 2, 3, 4, 5, 6])
+ >>> assert di.next() == Some(1)
+ >>> assert di.next_back() == Some(6)
+ >>> assert di.next_back() == Some(5)
+ >>> assert di.next() == Some(2)
+ >>> assert di.next() == Some(3)
+ >>> assert di.next() == Some(4)
+ >>> assert di.next() == nil
+ >>> assert di.next_back() == nil
+
+ ```
"""
...
@@ -53,36 +51,42 @@ def len(self) -> int:
Returns the exact remaining length of the diterum.
- Examples:
-
- >>> di = diterum([1, 2, 3, 4])
- >>> assert di.len() == 4
- >>> assert di.next() == Some(1)
- >>> assert di.len() == 3
- >>> assert di.next_back() == Some(4)
- >>> assert di.len() == 2
- >>> assert di.collect() == [2, 3]
- >>> assert di.next() == nil
- >>> assert di.len() == 0
+ **Examples:**
+
+ ```python
+ >>> di = diterum([1, 2, 3, 4])
+ >>> assert di.len() == 4
+ >>> assert di.next() == Some(1)
+ >>> assert di.len() == 3
+ >>> assert di.next_back() == Some(4)
+ >>> assert di.len() == 2
+ >>> assert di.collect() == [2, 3]
+ >>> assert di.next() == nil
+ >>> assert di.len() == 0
+
+ ```
"""
...
# Defined by Iterator
def rev(self) -> Rev[T_co]:
"""
- Reverses an diterum’s direction.
+ Reverses an diterum's direction.
Usually, iterums iterate from left to right. After using
[rev()][iterum.Diterum.rev], an iterum will instead iterate from
right to left.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> di = diterum([1, 2, 3]).rev()
+ >>> assert di.next() == Some(3)
+ >>> assert di.next() == Some(2)
+ >>> assert di.next() == Some(1)
+ >>> assert di.next() == nil
- >>> di = diterum([1, 2, 3]).rev()
- >>> assert di.next() == Some(3)
- >>> assert di.next() == Some(2)
- >>> assert di.next() == Some(1)
- >>> assert di.next() == nil
+ ```
"""
return Rev(self)
@@ -99,16 +103,23 @@ def rposition(self, predicate: Callable[[T_co], object], /) -> Option[int]:
[rposition()][iterum.Diterum.rposition] is short-circuiting; in other
words, it will stop processing as soon as it finds a `True`.
- Examples:
+ **Examples:**
- >>> di = diterum([1, 2, 3])
- >>> assert di.rposition(lambda x: x == 3) == Some(2)
- >>> assert di.rposition(lambda x: x == 5) == nil
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> assert di.rposition(lambda x: x == 3) == Some(2)
+ >>> assert di.rposition(lambda x: x == 5) == nil
- Short-circuiting after first `True`:
- >>> di = diterum([-1, 2, 3, 4])
- >>> assert di.rposition(lambda x: x >= 2) == Some(3)
- >>> assert di.next() == Some(-1)
+ ```
+
+ Short-circuiting after first `True`:
+
+ ```python
+ >>> di = diterum([-1, 2, 3, 4])
+ >>> assert di.rposition(lambda x: x >= 2) == Some(3)
+ >>> assert di.next() == Some(-1)
+
+ ```
"""
len = self.len()
return self.rev().position(predicate).map(lambda x: len - x - 1)
@@ -131,19 +142,30 @@ def nth_back(self, n: int, /) -> Option[T_co]:
[nth_back()][iterum.Diterum.nth_back] will return [nil][iterum.nil] if n
is greater than or equal to the length of the diterum.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> assert di.nth_back(2) == Some(1)
- >>> di = diterum([1, 2, 3])
- >>> assert di.nth_back(2) == Some(1)
+ ```
- Does not rewind:
- >>> di = diterum([1, 2, 3])
- >>> assert di.nth_back(1) == Some(2)
- >>> assert di.nth_back(1) == nil
+ Does not rewind:
- Returns [nil][iterum.nil] if there are less than `n + 1` elements:
- >>> di = diterum([1, 2, 3])
- >>> assert di.nth_back(10) == nil
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> assert di.nth_back(1) == Some(2)
+ >>> assert di.nth_back(1) == nil
+
+ ```
+
+ Returns [nil][iterum.nil] if there are less than `n + 1` elements:
+
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> assert di.nth_back(10) == nil
+
+ ```
"""
return self.rev().nth(n)
@@ -160,29 +182,36 @@ def rfind(self, predicate: Callable[[T_co], object], /) -> Option[T_co]:
[rfind()][iterum.Diterum.rfind] is short-circuiting; in other words, it
will stop processing as soon as the closure returns `True`.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> assert di.rfind(lambda x: x == 2) == Some(2)
+ >>> assert di.rfind(lambda x: x == 5) == nil
- >>> di = diterum([1, 2, 3])
- >>> assert di.rfind(lambda x: x == 2) == Some(2)
- >>> assert di.rfind(lambda x: x == 5) == nil
+ ```
- Stops at first `True`:
- >>> di = diterum([1, 2, 3])
- >>> assert di.rfind(lambda x: x == 2) == Some(2)
- >>> assert di.next_back() == Some(1)
+ Stops at first `True`:
+
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> assert di.rfind(lambda x: x == 2) == Some(2)
+ >>> assert di.next_back() == Some(1)
+
+ ```
"""
return self.rev().find(predicate)
def rfold(self, init: U, f: Callable[[U, T_co], U], /) -> U:
"""
- A diterum method that reduces the diterum’s elements to a single,
+ A diterum method that reduces the diterum's elements to a single,
final value, starting from the back.
This is the reverse version of [Iterum.fold()][iterum.Iterum.fold]:
it takes elements starting from the back of the diterum.
[rfold()][iterum.Diterum.rfold] takes two arguments: an initial value,
- and a closure with two arguments: an ‘accumulator’, and an element. The
+ and a closure with two arguments: an 'accumulator', and an element. The
closure returns the value that the accumulator should have for the next
iteration.
@@ -191,13 +220,16 @@ def rfold(self, init: U, f: Callable[[U, T_co], U], /) -> U:
After applying this closure to every element of the diterum,
[rfold()][iterum.Diterum.rfold] returns the accumulator.
- Examples:
+ **Examples:**
- >>> di = diterum([1, 2, 3])
- >>> sum = di.rfold(0, lambda acc, x: acc + x)
- >>> assert sum == 6
+ ```python
+ >>> di = diterum([1, 2, 3])
+ >>> sum = di.rfold(0, lambda acc, x: acc + x)
+ >>> assert sum == 6
- rfold is right-associtive:
+ ```
+
+ ??? note "rfold is right-associtive"
```python
>>> numbers = [1, 2, 3, 4, 5]
@@ -221,11 +253,14 @@ def try_rfold(
This is the reverse version of [Iterum.try_fold()][iterum.Iterum.try_fold]:
it takes elements starting from the back of the diterum.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> di = diterum(["1", "2", "3"])
+ >>> sum = di.try_rfold(0, lambda acc, x: acc + int(x), exception=TypeError)
+ >>> assert sum == Some(6)
- >>> di = diterum(["1", "2", "3"])
- >>> sum = di.try_rfold(0, lambda acc, x: acc + int(x), exception=TypeError)
- >>> assert sum == Some(6)
+ ```
"""
return self.rev().try_fold(init, f, exception=exception)
@@ -257,30 +292,39 @@ class diterum(Diterum[T_co]):
"""
Implements a [Diterum][iterum.Diterum] interface from a sequence.
- Examples:
+ **Examples:**
- >>> itr = diterum([1, 2, 3])
- >>> assert itr.next() == Some(1)
- >>> assert itr.next_back() == Some(3)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next_back() == nil
- >>> assert itr.next() == nil
+ ```python
+ >>> itr = diterum([1, 2, 3])
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next_back() == Some(3)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next_back() == nil
+ >>> assert itr.next() == nil
+
+ ```
- >>> itr = diterum([1, 2, 3])
- >>> assert itr.rfold(0, lambda acc, x: acc*2 + x) == 17
-
- >>> x = [0, 1, 2, 3, 4]
- >>> y = (
- ... diterum(x)
- ... .rev()
- ... .map(lambda x: x**2 + 1)
- ... .filter(lambda x: x % 2)
- ... .collect()
- ... )
- >>> assert y == [17, 5, 1]
+ ```python
+ >>> itr = diterum([1, 2, 3])
+ >>> assert itr.rfold(0, lambda acc, x: acc*2 + x) == 17
+
+ ```
+
+ ```python
+ >>> x = [0, 1, 2, 3, 4]
+ >>> y = (
+ ... diterum(x)
+ ... .rev()
+ ... .map(lambda x: x**2 + 1)
+ ... .filter(lambda x: x % 2)
+ ... .collect()
+ ... )
+ >>> assert y == [17, 5, 1]
+
+ ```
"""
- __slots__ = ("_seq", "_front", "_back")
+ __slots__ = ("_back", "_front", "_seq")
def __init__(self, __seq: Sequence[T_co], /) -> None:
self._seq = __seq
@@ -292,12 +336,15 @@ def next(self) -> Option[T_co]:
Returns the next value in the sequence from the front if present,
otherwise [nil][iterum.nil].
- Examples:
+ **Examples:**
- >>> itr = diterum([1, 2])
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
+ ```python
+ >>> itr = diterum([1, 2])
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
+
+ ```
"""
if self._back < self._front:
@@ -312,12 +359,15 @@ def next_back(self) -> Option[T_co]:
Returns the next value in the sequence from the back if present,
otherwise [nil][iterum.nil].
- Examples:
+ **Examples:**
+
+ ```python
+ >>> itr = diterum([1, 2])
+ >>> assert itr.next_back() == Some(2)
+ >>> assert itr.next_back() == Some(1)
+ >>> assert itr.next_back() == nil
- >>> itr = diterum([1, 2])
- >>> assert itr.next_back() == Some(2)
- >>> assert itr.next_back() == Some(1)
- >>> assert itr.next_back() == nil
+ ```
"""
if self._back < self._front:
@@ -331,16 +381,19 @@ def len(self) -> int:
"""
Returns the remaining length of the sequence.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> itr = diterum([1, 2])
+ >>> assert itr.len() == 2
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.len() == 1
+ >>> assert itr.next_back() == Some(2)
+ >>> assert itr.len() == 0
+ >>> assert itr.next() == nil
+ >>> assert itr.len() == 0
- >>> itr = diterum([1, 2])
- >>> assert itr.len() == 2
- >>> assert itr.next() == Some(1)
- >>> assert itr.len() == 1
- >>> assert itr.next_back() == Some(2)
- >>> assert itr.len() == 0
- >>> assert itr.next() == nil
- >>> assert itr.len() == 0
+ ```
"""
if self._back < self._front:
diff --git a/iterum/_helpers.py b/src/iterum/_helpers.py
similarity index 100%
rename from iterum/_helpers.py
rename to src/iterum/_helpers.py
diff --git a/iterum/_iterum.py b/src/iterum/_iterum.py
similarity index 61%
rename from iterum/_iterum.py
rename to src/iterum/_iterum.py
index c64dcec..90d4255 100644
--- a/iterum/_iterum.py
+++ b/src/iterum/_iterum.py
@@ -3,29 +3,22 @@
import builtins
import itertools
from abc import abstractmethod
-from collections.abc import Callable
-from collections.abc import Iterable
-from collections.abc import Iterator
+from collections.abc import Callable, Iterable, Iterator
from dataclasses import dataclass
-from typing import Generic
-from typing import overload
-from typing import TYPE_CHECKING
-from typing import TypeVar
+from typing import TYPE_CHECKING, TypeVar, overload
from ._helpers import check_methods
-from ._notset import NotSet
-from ._notset import NotSetType
-from ._option import Nil
-from ._option import nil
-from ._option import Option
-from ._option import Some
+from ._notset import NotSet, NotSetType
+from ._option import Nil, Option, Some, nil
from ._ordering import Ordering
if TYPE_CHECKING:
- from ._type_helpers import SupportsRichComparison
- from ._type_helpers import SupportsMulT
- from ._type_helpers import SupportsRichComparisonT
- from ._type_helpers import SupportsSumNoDefaultT
+ from ._type_helpers import (
+ SupportsMulT,
+ SupportsRichComparison,
+ SupportsRichComparisonT,
+ SupportsSumNoDefaultT,
+ )
T_co = TypeVar("T_co", covariant=True)
@@ -52,24 +45,40 @@ def next(self) -> Option[T_co]:
Returns [nil][iterum.nil] when iteration is finished.
Individual iterum implementations may choose to resume iteration,
- and so calling [next()][iterum.Iterum.next] again may or may not eventually start returning
- [Some(Item)][iterum.Some] again at some point.
+ and so calling [next()][iterum.Iterum.next] again may or may not
+ eventually start returning [Some(Item)][iterum.Some] again at some
+ point.
- Examples:
+ **Examples:**
- >>> itr = iterum([1, 2, 3])
+ ```python
+ >>> itr = iterum([1, 2, 3])
- A call to next() returns the next value...
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == Some(3)
+ ```
- ... and then [nil][iterum.nil] once it's over.
- >>> assert itr.next() == nil
+ A call to next() returns the next value...
- More calls may or may not return [nil][iterum.nil]. Here, they always will.
- >>> assert itr.next() == nil
- >>> assert itr.next() == nil
+ ```python
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == Some(3)
+
+ ```
+
+ ... and then [nil][iterum.nil] once it's over.
+
+ ```python
+ >>> assert itr.next() == nil
+
+ ```
+
+ More calls may or may not return [nil][iterum.nil]. Here, they always will.
+
+ ```python
+ >>> assert itr.next() == nil
+ >>> assert itr.next() == nil
+
+ ```
"""
return nil
@@ -97,16 +106,23 @@ def all(self, f: Callable[[T_co], object], /) -> bool:
An empty iterum returns `True`.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> assert iterum(a).all(lambda x: x > 0)
+ >>> assert not iterum(a).all(lambda x: x > 2)
+
+ ```
+
+ Stopping at the first `False`:
- >>> a = [1, 2, 3]
- >>> assert iterum(a).all(lambda x: x > 0)
- >>> assert not iterum(a).all(lambda x: x > 2)
+ ```python
+ >>> itr = iterum([1, 2, 3])
+ >>> assert not itr.all(lambda x: x != 2)
+ >>> assert itr.next() == Some(3)
- Stopping at the first `False`:
- >>> itr = iterum([1, 2, 3])
- >>> assert not itr.all(lambda x: x != 2)
- >>> assert itr.next() == Some(3)
+ ```
"""
return all(map(f, self))
@@ -125,19 +141,29 @@ def any(self, f: Callable[[T_co], object], /) -> bool:
An empty iterum returns `False`.
- Examples:
+ **Examples:**
- >>> a = [1, 2, 3]
- >>> assert iterum(a).any(lambda x: x > 0)
- >>> assert not iterum(a).any(lambda x: x > 5)
+ ```python
+ >>> a = [1, 2, 3]
+ >>> assert iterum(a).any(lambda x: x > 0)
+ >>> assert not iterum(a).any(lambda x: x > 5)
+ ```
- Stopping at the first `True`:
- >>> itr = iterum([1, 2, 3])
- >>> assert itr.any(lambda x: x != 2)
+ Stopping at the first `True`:
- itr still has more elements.
- >>> assert itr.next() == Some(2)
+ ```python
+ >>> itr = iterum([1, 2, 3])
+ >>> assert itr.any(lambda x: x != 2)
+
+ ```
+
+ itr still has more elements.
+
+ ```python
+ >>> assert itr.next() == Some(2)
+
+ ```
"""
return any(map(f, self))
@@ -151,32 +177,33 @@ def chain(self: Iterum[T_co], other: Iterable[T_co], /) -> Chain[T_co]:
In other words, it links two iterables together, in a chain.
- Examples:
+ **Examples:**
- >>> a1 = [1, 2, 3]
- >>> a2 = [4, 5, 6]
- >>> itr = iterum(a1).chain(a2)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == Some(3)
- >>> assert itr.next() == Some(4)
- >>> assert itr.next() == Some(5)
- >>> assert itr.next() == Some(6)
- >>> assert itr.next() == nil
+ ```python
+ >>> a1 = [1, 2, 3]
+ >>> a2 = [4, 5, 6]
+ >>> itr = iterum(a1).chain(a2)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == Some(3)
+ >>> assert itr.next() == Some(4)
+ >>> assert itr.next() == Some(5)
+ >>> assert itr.next() == Some(6)
+ >>> assert itr.next() == nil
+
+ ```
"""
return Chain(self, other)
@overload
def cmp(
self: Iterum[SupportsRichComparison], other: Iterable[object], /
- ) -> Ordering:
- ...
+ ) -> Ordering: ...
@overload
def cmp(
self: Iterum[object], other: Iterable[SupportsRichComparison], /
- ) -> Ordering:
- ...
+ ) -> Ordering: ...
def cmp(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -187,11 +214,14 @@ def cmp(
Lexicographically compares the elements of this Iterator with those of
another.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> assert iterum([1]).cmp([1]) == Ordering.Equal
+ >>> assert iterum([1, 2]).cmp([1]) == Ordering.Greater
+ >>> assert iterum([1]).cmp([1, 2]) == Ordering.Less
- >>> assert iterum([1]).cmp([1]) == Ordering.Equal
- >>> assert iterum([1, 2]).cmp([1]) == Ordering.Greater
- >>> assert iterum([1]).cmp([1, 2]) == Ordering.Less
+ ```
"""
other = iterum(other)
while True:
@@ -215,28 +245,24 @@ def cmp(
raise AssertionError("Unreachable!")
@overload
- def collect(self: Iterum[T_co], /) -> list[T_co]:
- ...
+ def collect(self: Iterum[T_co], /) -> list[T_co]: ...
@overload
- def collect(self: Iterum[T_co], container: type[list], /) -> list[T_co]:
- ...
+ def collect(self: Iterum[T_co], container: type[list], /) -> list[T_co]: ...
@overload
- def collect(self: Iterum[T_co], container: type[set], /) -> set[T_co]:
- ...
+ def collect(self: Iterum[T_co], container: type[set], /) -> set[T_co]: ...
@overload
- def collect(self: Iterum[T_co], container: type[tuple], /) -> tuple[T_co, ...]:
- ...
+ def collect(self: Iterum[T_co], container: type[tuple], /) -> tuple[T_co, ...]: ...
@overload
- def collect(self: Iterum[tuple[U, V]], container: type[dict], /) -> dict[U, V]:
- ...
+ def collect(self: Iterum[tuple[U, V]], container: type[dict], /) -> dict[U, V]: ...
@overload
- def collect(self: Iterum[T_co], container: Callable[[Iterable[T_co]], U], /) -> U:
- ...
+ def collect(
+ self: Iterum[T_co], container: Callable[[Iterable[T_co]], U], /
+ ) -> U: ...
def collect( # type: ignore
self: Iterum[T_co], container: Callable[[Iterable[T_co]], U] = list, /
@@ -248,13 +274,20 @@ def collect( # type: ignore
for mapping an iterable into any type. Most commonly this is a collection
type such as `list` or `set` but could also be a function such as `''.join`.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> doubled = iterum([1, 2, 3]).map(lambda x: x * 2).collect(list)
+ >>> assert doubled == [2, 4, 6]
+
+ ```
+
+ using `join` to collect an iterable of `str`
- >>> doubled = iterum([1, 2, 3]).map(lambda x: x * 2).collect(list)
- >>> assert doubled == [2, 4, 6]
+ ```python
+ >>> assert iterum("test").map(str.upper).collect("".join) == "TEST"
- using `join` to collect an iterable of `str`
- >>> assert iterum("test").map(str.upper).collect("".join) == "TEST"
+ ```
"""
return container(self)
@@ -267,10 +300,13 @@ def count(self) -> int:
Note that next has to be called at least once even if the iterum does
not have any elements.
- Examples:
+ **Examples:**
- >>> assert iterum([1, 2, 3]).count() == 3
- >>> assert iterum([1, 2, 3, 4, 5]).count() == 5
+ ```python
+ >>> assert iterum([1, 2, 3]).count() == 3
+ >>> assert iterum([1, 2, 3, 4, 5]).count() == 5
+
+ ```
"""
last = self.enumerate().last()
return last.map_or(0, lambda last: last[0] + 1)
@@ -284,17 +320,20 @@ def cycle(self: Iterum[T_co], /) -> Cycle[T_co]:
the beginning again. And again. And again. Forever. Note that in case
the original iterum is empty, the resulting iterum will also be empty.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> it = iterum(a).cycle()
+ >>> assert it.next() == Some(1)
+ >>> assert it.next() == Some(2)
+ >>> assert it.next() == Some(3)
+ >>> assert it.next() == Some(1)
+ >>> assert it.next() == Some(2)
+ >>> assert it.next() == Some(3)
+ >>> assert it.next() == Some(1)
- >>> a = [1, 2, 3]
- >>> it = iterum(a).cycle()
- >>> assert it.next() == Some(1)
- >>> assert it.next() == Some(2)
- >>> assert it.next() == Some(3)
- >>> assert it.next() == Some(1)
- >>> assert it.next() == Some(2)
- >>> assert it.next() == Some(3)
- >>> assert it.next() == Some(1)
+ ```
"""
return Cycle(self)
@@ -306,24 +345,29 @@ def enumerate(self: Iterum[T_co], /) -> Enumerate[T_co]:
The iterum returned yields pairs (i, val), where i is the current
index of iteration and val is the value returned by the iterum.
- Examples:
+ **Examples:**
- >>> a = ["a", "b", "c"]
- >>> it = iterum(a).enumerate()
- >>> assert it.next() == Some((0, "a"))
- >>> assert it.next() == Some((1, "b"))
- >>> assert it.next() == Some((2, "c"))
- >>> assert it.next() == nil
+ ```python
+ >>> a = ["a", "b", "c"]
+ >>> it = iterum(a).enumerate()
+ >>> assert it.next() == Some((0, "a"))
+ >>> assert it.next() == Some((1, "b"))
+ >>> assert it.next() == Some((2, "c"))
+ >>> assert it.next() == nil
+
+ ```
"""
return Enumerate(self)
@overload
- def eq(self: Iterum[SupportsRichComparison], other: Iterable[object], /) -> bool:
- ...
+ def eq(
+ self: Iterum[SupportsRichComparison], other: Iterable[object], /
+ ) -> bool: ...
@overload
- def eq(self: Iterum[object], other: Iterable[SupportsRichComparison], /) -> bool:
- ...
+ def eq(
+ self: Iterum[object], other: Iterable[SupportsRichComparison], /
+ ) -> bool: ...
def eq(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -333,10 +377,13 @@ def eq(
"""
Determines if the elements of this Iterator are equal to those of another.
- Examples:
+ **Examples:**
- >>> assert iterum([1]).eq([1])
- >>> assert not iterum([1]).eq([1, 2])
+ ```python
+ >>> assert iterum([1]).eq([1])
+ >>> assert not iterum([1]).eq([1, 2])
+
+ ```
"""
cmp = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission
return cmp == Ordering.Equal
@@ -351,13 +398,16 @@ def filter(
Given an element the closure must return `True` or `False`. The returned
iterum will yield only the elements for which the closure returns `True`.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [0, 1, 2]
+ >>> it = iterum(a).filter(lambda x: x > 0)
+ >>> assert it.next() == Some(1)
+ >>> assert it.next() == Some(2)
+ >>> assert it.next() == nil
- >>> a = [0, 1, 2]
- >>> it = iterum(a).filter(lambda x: x > 0)
- >>> assert it.next() == Some(1)
- >>> assert it.next() == Some(2)
- >>> assert it.next() == nil
+ ```
Note that `it.filter(f).next()` is equivalent to `it.find(f)`.
"""
@@ -375,21 +425,24 @@ def filter_map(
[filter_map][iterum.Iterum.filter_map] can be used to make chains of
[filter][iterum.Iterum.filter] and [map][iterum.Iterum.map] more concise.
- Examples:
-
- >>> def parse2int(x: str) -> Option[int]:
- ... try:
- ... value = int(x)
- ... except ValueError:
- ... return nil
- ... else:
- ... return Some(value)
- ...
- >>> a = ["1", "two", "NaN", "four", "5"]
- >>> it = iterum(a).filter_map(parse2int)
- >>> assert it.next() == Some(1)
- >>> assert it.next() == Some(5)
- >>> assert it.next() == nil
+ **Examples:**
+
+ ```python
+ >>> def parse2int(x: str) -> Option[int]:
+ ... try:
+ ... value = int(x)
+ ... except ValueError:
+ ... return nil
+ ... else:
+ ... return Some(value)
+ ...
+ >>> a = ["1", "two", "NaN", "four", "5"]
+ >>> it = iterum(a).filter_map(parse2int)
+ >>> assert it.next() == Some(1)
+ >>> assert it.next() == Some(5)
+ >>> assert it.next() == nil
+
+ ```
"""
return FilterMap(self, predicate)
@@ -408,16 +461,23 @@ def find(self, predicate: Callable[[T_co], object], /) -> Option[T_co]:
If you need the index of the element, see [position()][iterum.Iterum.position].
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> assert iterum(a).find(lambda x: x == 2) == Some(2)
+ >>> assert iterum(a).find(lambda x: x == 5) == nil
- >>> a = [1, 2, 3]
- >>> assert iterum(a).find(lambda x: x == 2) == Some(2)
- >>> assert iterum(a).find(lambda x: x == 5) == nil
+ ```
- Stopping at the first `True`:
- >>> it = iterum([1, 2, 3])
- >>> assert it.find(lambda x: x == 2) == Some(2)
- >>> assert it.next() == Some(3)
+ Stopping at the first `True`:
+
+ ```python
+ >>> it = iterum([1, 2, 3])
+ >>> assert it.find(lambda x: x == 2) == Some(2)
+ >>> assert it.next() == Some(3)
+
+ ```
Note that `it.find(f)` is equivalent to `it.filter(f).next()`.
"""
@@ -431,19 +491,22 @@ def find_map(self, predicate: Callable[[T_co], Option[U]], /) -> Option[U]:
Applies function to the elements of iterum and returns the first
non-nil result.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> def parse2int(x: str) -> Option[int]:
+ ... try:
+ ... value = int(x)
+ ... except ValueError:
+ ... return nil
+ ... else:
+ ... return Some(value)
+ ...
+ >>> a = ["lol", "NaN", "2", "5"]
+ >>> first_number = iterum(a).find_map(parse2int)
+ >>> assert first_number == Some(2)
- >>> def parse2int(x: str) -> Option[int]:
- ... try:
- ... value = int(x)
- ... except ValueError:
- ... return nil
- ... else:
- ... return Some(value)
- ...
- >>> a = ["lol", "NaN", "2", "5"]
- >>> first_number = iterum(a).find_map(parse2int)
- >>> assert first_number == Some(2)
+ ```
Note that `iter.find_map(f)` is equivalent to `iter.filter_map(f).next()`.
"""
@@ -455,17 +518,20 @@ def flat_map(self, f: Callable[[T_co], Iterable[U]], /) -> FlatMap[U]:
The [map][iterum.Iterum.map] adapter is very useful, but only when the
closure argument produces values. If it produces an iterum instead,
- there’s an extra layer of indirection.
+ there's an extra layer of indirection.
[flat_map()][iterum.Iterum.flat_map] will remove this extra layer on its own.
You can think of `flat_map(f)` as the semantic equivalent of mapping, and
then flattening as in `map(f).flatten()`.
- Examples:
+ **Examples:**
- >>> words = ["alpha", "beta", "gamma"]
- >>> merged = iterum(words).flat_map(iterum).collect("".join)
- >>> assert merged == "alphabetagamma"
+ ```python
+ >>> words = ["alpha", "beta", "gamma"]
+ >>> merged = iterum(words).flat_map(iterum).collect("".join)
+ >>> assert merged == "alphabetagamma"
+
+ ```
"""
return FlatMap(self, f)
@@ -476,16 +542,23 @@ def flatten(self: Iterum[Iterable[U]]) -> Flatten[U]:
This is useful when you have an iterum of iterables and you want to
remove one level of indirection.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> data = [[1, 2, 3, 4], [5, 6]]
+ >>> flattened = iterum(data).flatten().collect(list)
+ >>> assert flattened == [1, 2, 3, 4, 5, 6]
+
+ ```
- >>> data = [[1, 2, 3, 4], [5, 6]]
- >>> flattened = iterum(data).flatten().collect(list)
- >>> assert flattened == [1, 2, 3, 4, 5, 6]
+ Mapping and then flattening:
- Mapping and then flattening:
- >>> words = ["alpha", "beta", "gamma"]
- >>> merged = iterum(words).map(iterum).flatten().collect("".join)
- >>> assert merged == "alphabetagamma"
+ ```python
+ >>> words = ["alpha", "beta", "gamma"]
+ >>> merged = iterum(words).map(iterum).flatten().collect("".join)
+ >>> assert merged == "alphabetagamma"
+
+ ```
"""
return Flatten(self)
@@ -495,33 +568,37 @@ def fold(self, init: U, f: Callable[[U, T_co], U], /) -> U:
returning the final result.
[fold()][iterum.Iterum.fold] takes two arguments: an initial value, and
- a closure with two arguments: an ‘accumulator’, and an element. The
- closure returns the value that the accumulator should have for the next iteration.
+ a closure with two arguments: an 'accumulator', and an element. The
+ closure returns the value that the accumulator should have for the
+ next iteration.
The initial value is the value the accumulator will have on the first call.
After applying this closure to every element of the iterum, fold()
returns the accumulator.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> sum = iterum(a).fold(0, lambda acc, x: acc + x)
+ >>> assert sum == 6
- >>> a = [1, 2, 3]
- >>> sum = iterum(a).fold(0, lambda acc, x: acc + x)
- >>> assert sum == 6
+ ```
- Let's walk through each step of the iteration here:
+ Let's walk through each step of the iteration here:
- | element | acc | x | result |
- | ------- | --- | - | ------ |
- | | 0 | | |
- | 1 | 0 | 1 | 1 |
- | 2 | 1 | 2 | 3 |
- | 3 | 3 | 3 | 6 |
+ | element | acc | x | result |
+ | ------- | --- | - | ------ |
+ | | 0 | | |
+ | 1 | 0 | 1 | 1 |
+ | 2 | 1 | 2 | 3 |
+ | 3 | 3 | 3 | 6 |
- And so, our final result, 6.
+ And so, our final result, 6.
- fold is left-associative:
+ ??? note "fold is left-associative"
```python
>>> numbers = [1, 2, 3, 4, 5]
@@ -541,11 +618,14 @@ def for_each(self, f: Callable[[T_co], object], /) -> None:
For loops are more idiomatic... but who cares!
- Examples:
+ **Examples:**
- >>> v = []
- >>> seq(5).map(lambda x: x * 2 + 1).for_each(v.append)
- >>> assert v == [1, 3, 5, 7, 9]
+ ```python
+ >>> v = []
+ >>> seq(5).map(lambda x: x * 2 + 1).for_each(v.append)
+ >>> assert v == [1, 3, 5, 7, 9]
+
+ ```
"""
for x in self:
f(x)
@@ -559,37 +639,49 @@ def fuse(self) -> Fuse[T_co]:
an iterum, ensuring that after a [nil][iterum.nil] is given, it will
always return [nil][iterum.nil] forever.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> class Alternator(Iterator[int]):
+ ... def __init__(self) -> None:
+ ... self.i = 0
+ ... def __next__(self) -> int:
+ ... self.i += 1
+ ... if self.i % 5:
+ ... return self.i
+ ... else:
+ ... raise StopIteration()
+ ...
+
+ ```
+
+ ```python
+ >>> it = iterum(Alternator())
+ >>> assert list(it) == [1, 2, 3, 4]
+ >>> assert list(it) == [6, 7, 8, 9]
+ >>> assert list(it) == [11, 12, 13, 14]
- >>> class Alternator(Iterator[int]):
- ... def __init__(self) -> None:
- ... self.i = 0
- ... def __next__(self) -> int:
- ... self.i += 1
- ... if self.i % 5:
- ... return self.i
- ... else:
- ... raise StopIteration()
+ ```
- >>> it = iterum(Alternator())
- >>> assert list(it) == [1, 2, 3, 4]
- >>> assert list(it) == [6, 7, 8, 9]
- >>> assert list(it) == [11, 12, 13, 14]
+ ```python
+ >>> it = it.fuse()
+ >>> assert list(it) == [16, 17, 18, 19]
+ >>> assert list(it) == []
+ >>> assert list(it) == []
- >>> it = it.fuse()
- >>> assert list(it) == [16, 17, 18, 19]
- >>> assert list(it) == []
- >>> assert list(it) == []
+ ```
"""
return Fuse(self)
@overload
- def ge(self: Iterum[SupportsRichComparison], other: Iterable[object], /) -> bool:
- ...
+ def ge(
+ self: Iterum[SupportsRichComparison], other: Iterable[object], /
+ ) -> bool: ...
@overload
- def ge(self: Iterum[object], other: Iterable[SupportsRichComparison], /) -> bool:
- ...
+ def ge(
+ self: Iterum[object], other: Iterable[SupportsRichComparison], /
+ ) -> bool: ...
def ge(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -600,23 +692,28 @@ def ge(
Determines if the elements of this Iterator are lexicographically
greater than or equal to those of another.
- Examples:
+ **Examples:**
- >>> assert iterum([1]).ge([1])
- >>> assert not iterum([1]).ge([1, 2])
- >>> assert iterum([1, 2]).ge([1])
- >>> assert iterum([1, 2]).ge([1, 2])
+ ```python
+ >>> assert iterum([1]).ge([1])
+ >>> assert not iterum([1]).ge([1, 2])
+ >>> assert iterum([1, 2]).ge([1])
+ >>> assert iterum([1, 2]).ge([1, 2])
+
+ ```
"""
cmp = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission
return cmp in (Ordering.Greater, Ordering.Equal)
@overload
- def gt(self: Iterum[SupportsRichComparison], other: Iterable[object], /) -> bool:
- ...
+ def gt(
+ self: Iterum[SupportsRichComparison], other: Iterable[object], /
+ ) -> bool: ...
@overload
- def gt(self: Iterum[object], other: Iterable[SupportsRichComparison], /) -> bool:
- ...
+ def gt(
+ self: Iterum[object], other: Iterable[SupportsRichComparison], /
+ ) -> bool: ...
def gt(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -627,12 +724,15 @@ def gt(
Determines if the elements of this Iterator are lexicographically
greater than those of another.
- Examples:
+ **Examples:**
- >>> assert not iterum([1]).gt([1])
- >>> assert not iterum([1]).gt([1, 2])
- >>> assert iterum([1, 2]).gt([1])
- >>> assert not iterum([1, 2]).gt([1, 2])
+ ```python
+ >>> assert not iterum([1]).gt([1])
+ >>> assert not iterum([1]).gt([1, 2])
+ >>> assert iterum([1, 2]).gt([1])
+ >>> assert not iterum([1, 2]).gt([1, 2])
+
+ ```
"""
cmp = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission
return cmp == Ordering.Greater
@@ -641,41 +741,47 @@ def inspect(self, f: Callable[[T_co], object], /) -> Inspect[T_co]:
"""
Does something with each element of an iterum, passing the value on.
- When using iterums, you’ll often chain several of them together. While
- working on such code, you might want to check out what’s happening at
+ When using iterums, you'll often chain several of them together. While
+ working on such code, you might want to check out what's happening at
various parts in the pipeline. To do that, insert a call to
[inspect()][iterum.Iterum.inspect].
- Examples:
-
- >>> s = (
- ... iterum([1, 4, 2, 3])
- ... .inspect(lambda x: print(f"about to filter: {x}"))
- ... .filter(lambda x: x % 2 == 0)
- ... .inspect(lambda x: print(f"made it through filter: {x}"))
- ... .fold(0, lambda sum, i: sum + i)
- ... )
- ...
- about to filter: 1
- about to filter: 4
- made it through filter: 4
- about to filter: 2
- made it through filter: 2
- about to filter: 3
- >>> s
- 6
-
- >>> a = [1, 2, 3]
- >>> b = []
- >>> c = (
- ... iterum(a)
- ... .map(lambda x: x * 2)
- ... .inspect(b.append)
- ... .take_while(lambda x: x < 5)
- ... .collect(list)
- ... )
- >>> assert b == [2, 4, 6]
- >>> assert c == [2, 4]
+ **Examples:**
+
+ ```python
+ >>> s = (
+ ... iterum([1, 4, 2, 3])
+ ... .inspect(lambda x: print(f"about to filter: {x}"))
+ ... .filter(lambda x: x % 2 == 0)
+ ... .inspect(lambda x: print(f"made it through filter: {x}"))
+ ... .fold(0, lambda sum, i: sum + i)
+ ... )
+ ...
+ about to filter: 1
+ about to filter: 4
+ made it through filter: 4
+ about to filter: 2
+ made it through filter: 2
+ about to filter: 3
+ >>> s
+ 6
+
+ ```
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> b = []
+ >>> c = (
+ ... iterum(a)
+ ... .map(lambda x: x * 2)
+ ... .inspect(b.append)
+ ... .take_while(lambda x: x < 5)
+ ... .collect(list)
+ ... )
+ >>> assert b == [2, 4, 6]
+ >>> assert c == [2, 4]
+
+ ```
"""
return Inspect(self, f)
@@ -688,10 +794,13 @@ def last(self) -> Option[T_co]:
element. After [nil][iterum.nil] is returned, last() will then return
the last element it saw.
- Examples:
+ **Examples:**
- >>> assert iterum([1, 2, 3]).last() == Some(3)
- >>> assert iterum([1, 2, 3, 4, 5]).last() == Some(5)
+ ```python
+ >>> assert iterum([1, 2, 3]).last() == Some(3)
+ >>> assert iterum([1, 2, 3, 4, 5]).last() == Some(5)
+
+ ```
"""
last = nil
while (nxt := self.next()) is not nil:
@@ -700,12 +809,14 @@ def last(self) -> Option[T_co]:
return last
@overload
- def le(self: Iterum[SupportsRichComparison], other: Iterable[object], /) -> bool:
- ...
+ def le(
+ self: Iterum[SupportsRichComparison], other: Iterable[object], /
+ ) -> bool: ...
@overload
- def le(self: Iterum[object], other: Iterable[SupportsRichComparison], /) -> bool:
- ...
+ def le(
+ self: Iterum[object], other: Iterable[SupportsRichComparison], /
+ ) -> bool: ...
def le(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -716,23 +827,28 @@ def le(
Determines if the elements of this Iterator are lexicographically less
or equal to those of another.
- Examples:
+ **Examples:**
- >>> assert iterum([1]).le([1])
- >>> assert iterum([1]).le([1, 2])
- >>> assert not iterum([1, 2]).le([1])
- >>> assert iterum([1, 2]).le([1, 2])
+ ```python
+ >>> assert iterum([1]).le([1])
+ >>> assert iterum([1]).le([1, 2])
+ >>> assert not iterum([1, 2]).le([1])
+ >>> assert iterum([1, 2]).le([1, 2])
+
+ ```
"""
cmp = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission
return cmp in (Ordering.Less, Ordering.Equal)
@overload
- def lt(self: Iterum[SupportsRichComparison], other: Iterable[object], /) -> bool:
- ...
+ def lt(
+ self: Iterum[SupportsRichComparison], other: Iterable[object], /
+ ) -> bool: ...
@overload
- def lt(self: Iterum[object], other: Iterable[SupportsRichComparison], /) -> bool:
- ...
+ def lt(
+ self: Iterum[object], other: Iterable[SupportsRichComparison], /
+ ) -> bool: ...
def lt(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -743,12 +859,15 @@ def lt(
Determines if the elements of this Iterator are lexicographically less
than those of another.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> assert not iterum([1]).lt([1])
+ >>> assert iterum([1]).lt([1, 2])
+ >>> assert not iterum([1, 2]).lt([1])
+ >>> assert not iterum([1, 2]).lt([1, 2])
- >>> assert not iterum([1]).lt([1])
- >>> assert iterum([1]).lt([1, 2])
- >>> assert not iterum([1, 2]).lt([1])
- >>> assert not iterum([1, 2]).lt([1, 2])
+ ```
"""
cmp = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission
return cmp == Ordering.Less
@@ -762,14 +881,17 @@ def map(self, f: Callable[[T_co], U], /) -> Map[U]:
means of its argument. It produces a new iterum which calls this
closure on each element of the original iterum.
- Examples:
+ **Examples:**
- >>> a = [1, 2, 3]
- >>> itr = iterum(a).map(lambda x: x * 2)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == Some(4)
- >>> assert itr.next() == Some(6)
- >>> assert itr.next() == nil
+ ```python
+ >>> a = [1, 2, 3]
+ >>> itr = iterum(a).map(lambda x: x * 2)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == Some(4)
+ >>> assert itr.next() == Some(6)
+ >>> assert itr.next() == nil
+
+ ```
"""
return Map(self, f)
@@ -781,28 +903,35 @@ def map_while(self, predicate: Callable[[T_co], Option[U]], /) -> MapWhile[U]:
It will call this closure on each element of the iterum, and yield
elements while it returns [Some(_)][iterum.Some].
- Examples:
-
- >>> from functools import partial
- >>> def checked_div(num: int, dem: int) -> Option[int]:
- ... try:
- ... return Some(num // dem)
- ... except ZeroDivisionError:
- ... return nil
- ...
- >>> a = [-1, 4, 0, 1]
- >>> it = iterum(a).map_while(partial(checked_div, 16))
- >>> assert it.next() == Some(-16)
- >>> assert it.next() == Some(4)
- >>> assert it.next() == nil
-
-
- Stops after first [nil][iterum.nil]:
- >>> a = [0, 1, 2, -3, 4, 5, -6]
- >>> it = iterum(a).map_while(lambda x: Some(x) if x >= 0 else nil)
- >>> vec = it.collect(list)
- >>> assert vec == [0, 1, 2]
- >>> assert it.next() == nil
+ **Examples:**
+
+ ```python
+ >>> from functools import partial
+ >>> def checked_div(num: int, dem: int) -> Option[int]:
+ ... try:
+ ... return Some(num // dem)
+ ... except ZeroDivisionError:
+ ... return nil
+ ...
+ >>> a = [-1, 4, 0, 1]
+ >>> it = iterum(a).map_while(partial(checked_div, 16))
+ >>> assert it.next() == Some(-16)
+ >>> assert it.next() == Some(4)
+ >>> assert it.next() == nil
+
+ ```
+
+
+ Stops after first [nil][iterum.nil]:
+
+ ```python
+ >>> a = [0, 1, 2, -3, 4, 5, -6]
+ >>> it = iterum(a).map_while(lambda x: Some(x) if x >= 0 else nil)
+ >>> vec = it.collect(list)
+ >>> assert vec == [0, 1, 2]
+ >>> assert it.next() == nil
+
+ ```
"""
return MapWhile(self, predicate)
@@ -815,10 +944,13 @@ def max(
If several elements are equally maximum, the last element is returned.
If the iterum is empty, [nil][iterum.nil] is returned.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> assert iterum([1, 2, 3]).max() == Some(3)
+ >>> assert iterum([]).max() == nil
- >>> assert iterum([1, 2, 3]).max() == Some(3)
- >>> assert iterum([]).max() == nil
+ ```
"""
try:
return Some(builtins.max(self))
@@ -833,10 +965,13 @@ def max_by(self, compare: Callable[[T_co, T_co], Ordering], /) -> Option[T_co]:
If several elements are equally maximum, the last element is returned.
If the iterum is empty, [nil][iterum.nil] is returned.
- Examples:
+ **Examples:**
- >>> a = [-3, 0, 1, 5, -10]
- >>> assert iterum(a).max_by(Ordering.cmp).unwrap() == 5
+ ```python
+ >>> a = [-3, 0, 1, 5, -10]
+ >>> assert iterum(a).max_by(Ordering.cmp).unwrap() == 5
+
+ ```
"""
max_ = self.next()
if max_ is nil:
@@ -859,10 +994,13 @@ def max_by_key(
If several elements are equally maximum, the last element is returned.
If the iterum is empty, [nil][iterum.nil] is returned.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [-3, 0, 1, 5, -10]
+ >>> assert iterum(a).max_by_key(abs).unwrap() == -10
- >>> a = [-3, 0, 1, 5, -10]
- >>> assert iterum(a).max_by_key(abs).unwrap() == -10
+ ```
"""
def compare(x, y) -> Ordering:
@@ -881,10 +1019,13 @@ def min(
If several elements are equally minimum, the first element is returned.
If the iterum is empty, [nil][iterum.nil] is returned.
- Examples:
+ **Examples:**
- >>> assert iterum([1, 2, 3]).min() == Some(1)
- >>> assert iterum([]).min() == nil
+ ```python
+ >>> assert iterum([1, 2, 3]).min() == Some(1)
+ >>> assert iterum([]).min() == nil
+
+ ```
"""
try:
return Some(builtins.min(self))
@@ -899,10 +1040,13 @@ def min_by(self, compare: Callable[[T_co, T_co], Ordering], /) -> Option[T_co]:
If several elements are equally minimum, the first element is returned.
If the iterum is empty, [nil][iterum.nil] is returned.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [-3, 0, 1, 5, -10]
+ >>> assert iterum(a).min_by(Ordering.cmp).unwrap() == -10
- >>> a = [-3, 0, 1, 5, -10]
- >>> assert iterum(a).min_by(Ordering.cmp).unwrap() == -10
+ ```
"""
min_ = self.next()
if min_ is nil:
@@ -925,10 +1069,13 @@ def min_by_key(
If several elements are equally minimum, the first element is returned.
If the iterum is empty, [nil][iterum.nil] is returned.
- Examples:
+ **Examples:**
- >>> a = [-3, 0, 1, 5, -10]
- >>> assert iterum(a).min_by_key(abs).unwrap() == 0
+ ```python
+ >>> a = [-3, 0, 1, 5, -10]
+ >>> assert iterum(a).min_by_key(abs).unwrap() == 0
+
+ ```
"""
def compare(x, y) -> Ordering:
@@ -939,12 +1086,14 @@ def compare(x, y) -> Ordering:
return self.min_by(compare)
@overload
- def ne(self: Iterum[SupportsRichComparison], other: Iterable[object], /) -> bool:
- ...
+ def ne(
+ self: Iterum[SupportsRichComparison], other: Iterable[object], /
+ ) -> bool: ...
@overload
- def ne(self: Iterum[object], other: Iterable[SupportsRichComparison], /) -> bool:
- ...
+ def ne(
+ self: Iterum[object], other: Iterable[SupportsRichComparison], /
+ ) -> bool: ...
def ne(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -954,10 +1103,13 @@ def ne(
"""
Determines if the elements of this Iterator are not equal to those of another.
- Examples:
+ **Examples:**
- >>> assert not iterum([1]).ne([1])
- >>> assert iterum([1]).ne([1, 2])
+ ```python
+ >>> assert not iterum([1]).ne([1])
+ >>> assert iterum([1]).ne([1, 2])
+
+ ```
"""
eq = self.eq(other) # type: ignore | reason: ask for forgiveness not permission
return not eq
@@ -966,30 +1118,42 @@ def nth(self, n: int, /) -> Option[T_co]:
"""
Returns the nth element of the iterum.
- Like most indexing operations, the count starts from zero, so [nth(0)][iterum.Iterum.nth]
- returns the first value, [nth(1)][iterum.Iterum.nth] the second, and so on.
+ Like most indexing operations, the count starts from zero, so
+ [nth(0)][iterum.Iterum.nth] returns the first value,
+ [nth(1)][iterum.Iterum.nth] the second, and so on.
Note that all preceding elements, as well as the returned element, will
be consumed from the iterum. That means that the preceding elements
- will be discarded, and also that calling [nth(0)][iterum.Iterum.nth] multiple times on the
- same iterum will return different elements.
+ will be discarded, and also that calling [nth(0)][iterum.Iterum.nth]
+ multiple times on the same iterum will return different elements.
[nth()][iterum.Iterum.nth] will return [nil][iterum.nil] if n is greater
than or equal to the length of the iterum.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> assert iterum(a).nth(1) == Some(2)
+
+ ```
+
+ Calling [nth][iterum.Iterum.nth] multiple times doesn't rewind the iterum:
+
+ ```python
+ >>> itr = iterum([1, 2, 3])
+ >>> assert itr.nth(1) == Some(2)
+ >>> assert itr.nth(1) == nil
+
+ ```
- >>> a = [1, 2, 3]
- >>> assert iterum(a).nth(1) == Some(2)
+ Returns [nil][iterum.nil] if there are less than `n + 1` elements:
- Calling [nth][iterum.Iterum.nth] multiple times doesn't rewind the iterum:
- >>> itr = iterum([1, 2, 3])
- >>> assert itr.nth(1) == Some(2)
- >>> assert itr.nth(1) == nil
+ ```python
+ >>> itr = iterum([1, 2, 3])
+ >>> assert itr.nth(3) == nil
- Returns [nil][iterum.nil] if there are less than `n + 1` elements:
- >>> itr = iterum([1, 2, 3])
- >>> assert itr.nth(3) == nil
+ ```
"""
for i, x in enumerate(self):
if i > n:
@@ -999,20 +1163,17 @@ def nth(self, n: int, /) -> Option[T_co]:
return nil
@overload
- def partial_cmp(
+ def partial_cmp( # type: ignore
self: Iterum[SupportsRichComparison], other: Iterable[object], /
- ) -> Some[Ordering]:
- ...
+ ) -> Some[Ordering]: ...
@overload
def partial_cmp(
self: Iterum[object], other: Iterable[SupportsRichComparison], /
- ) -> Some[Ordering]:
- ...
+ ) -> Some[Ordering]: ...
@overload
- def partial_cmp(self: Iterum[object], other: Iterable[object], /) -> Nil:
- ...
+ def partial_cmp(self: Iterum[object], other: Iterable[object], /) -> Nil: ...
def partial_cmp(
self: Iterum[SupportsRichComparison] | Iterum[object],
@@ -1025,16 +1186,23 @@ def partial_cmp(
returning a result without comparing the remaining elements. As soon as
an order can be determined, the evaluation stops and a result is returned.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> assert iterum([1]).partial_cmp([1]) == Some(Ordering.Equal)
+ >>> assert iterum([1, 2]).partial_cmp([1]) == Some(Ordering.Greater)
+ >>> assert iterum([1]).partial_cmp([1, 2]) == Some(Ordering.Less)
+
+ ```
+
+ Results are determined by the order of evaluation:
- >>> assert iterum([1]).partial_cmp([1]) == Some(Ordering.Equal)
- >>> assert iterum([1, 2]).partial_cmp([1]) == Some(Ordering.Greater)
- >>> assert iterum([1]).partial_cmp([1, 2]) == Some(Ordering.Less)
+ ```python
+ >>> assert iterum([1, None]).partial_cmp([2, nil]) == Some(Ordering.Less)
+ >>> assert iterum([2, None]).partial_cmp([1, nil]) == Some(Ordering.Greater)
+ >>> assert iterum([None, 1]).partial_cmp([2, None]) == nil
- Results are determined by the order of evaluation:
- >>> assert iterum([1, None]).partial_cmp([2, nil]) == Some(Ordering.Less)
- >>> assert iterum([2, None]).partial_cmp([1, nil]) == Some(Ordering.Greater)
- >>> assert iterum([None, 1]).partial_cmp([2, None]) == nil
+ ```
"""
try:
value = self.cmp(other) # type: ignore | reason: ask for forgiveness not permission
@@ -1046,38 +1214,32 @@ def partial_cmp(
@overload
def partition(
self, f: Callable[[T_co], object], /
- ) -> tuple[list[T_co], list[T_co]]:
- ...
+ ) -> tuple[list[T_co], list[T_co]]: ...
@overload
def partition(
self, f: Callable[[T_co], object], container: type[list], /
- ) -> tuple[list[T_co], list[T_co]]:
- ...
+ ) -> tuple[list[T_co], list[T_co]]: ...
@overload
def partition(
self, f: Callable[[T_co], object], container: type[set], /
- ) -> tuple[set[T_co], set[T_co]]:
- ...
+ ) -> tuple[set[T_co], set[T_co]]: ...
@overload
def partition(
self, f: Callable[[T_co], object], container: type[tuple], /
- ) -> tuple[tuple[T_co, ...], tuple[T_co, ...]]:
- ...
+ ) -> tuple[tuple[T_co, ...], tuple[T_co, ...]]: ...
@overload
def partition(
self: Iterum[tuple[U, V]], f: Callable[[T_co], object], container: type[dict], /
- ) -> tuple[dict[U, V], dict[U, V]]:
- ...
+ ) -> tuple[dict[U, V], dict[U, V]]: ...
@overload
def partition(
self, f: Callable[[T_co], object], container: Callable[[Iterable[T_co]], U], /
- ) -> tuple[U, U]:
- ...
+ ) -> tuple[U, U]: ...
def partition( # type: ignore
self,
@@ -1093,12 +1255,15 @@ def partition( # type: ignore
returns a pair, all of the elements for which it returned `True`, and
all of the elements for which it returned `False`.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> even, odd = iterum(a).partition(lambda n: n % 2 == 0)
+ >>> assert even == [2]
+ >>> assert odd == [1, 3]
- >>> a = [1, 2, 3]
- >>> even, odd = iterum(a).partition(lambda n: n % 2 == 0)
- >>> assert even == [2]
- >>> assert odd == [1, 3]
+ ```
"""
matches, notmatches = [], []
for x in self:
@@ -1111,27 +1276,33 @@ def peekable(self) -> Peekable[T_co]:
Creates an iterum which provides a peek attribute for viewing
and setting the next element of the iterum without consuming it.
- Examples:
-
- >>> xs = [1, 2, 3]
- >>> itr = iterum(xs).peekable()
- >>> assert itr.peek == Some(1)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.peek == Some(3)
- >>> assert itr.peek == Some(3)
- >>> assert itr.next() == Some(3)
- >>> assert itr.peek == nil
- >>> assert itr.next() == nil
-
- >>> xs = [1, 2, 3]
- >>> itr = iterum(xs).peekable()
- >>> assert itr.peek == Some(1)
- >>> assert itr.peek == Some(1)
- >>> assert itr.next() == Some(1)
- >>> assert itr.peek == Some(2)
- >>> itr.peek = 1000
- >>> assert list(itr) == [1000, 3]
+ **Examples:**
+
+ ```python
+ >>> xs = [1, 2, 3]
+ >>> itr = iterum(xs).peekable()
+ >>> assert itr.peek == Some(1)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.peek == Some(3)
+ >>> assert itr.peek == Some(3)
+ >>> assert itr.next() == Some(3)
+ >>> assert itr.peek == nil
+ >>> assert itr.next() == nil
+
+ ```
+
+ ```python
+ >>> xs = [1, 2, 3]
+ >>> itr = iterum(xs).peekable()
+ >>> assert itr.peek == Some(1)
+ >>> assert itr.peek == Some(1)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.peek == Some(2)
+ >>> itr.peek = 1000
+ >>> assert list(itr) == [1000, 3]
+
+ ```
"""
return Peekable(self)
@@ -1148,17 +1319,22 @@ def position(self, predicate: Callable[[T_co], object], /) -> Option[int]:
[position()][iterum.Iterum.position] is short-circuiting; in other
words, it will stop processing as soon as it finds a `True`.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> assert iterum(a).position(lambda x: x == 2) == Some(1)
+ >>> assert iterum(a).position(lambda x: x == 5) == nil
- >>> a = [1, 2, 3]
- >>> assert iterum(a).position(lambda x: x == 2) == Some(1)
- >>> assert iterum(a).position(lambda x: x == 5) == nil
+ ```
+ ```python
+ >>> it = iterum([1, 2, 3, 4])
+ >>> assert it.position(lambda x: x >= 2) == Some(1)
+ >>> assert it.next() == Some(3)
+ >>> assert it.position(lambda x: x == 4) == Some(0)
- >>> it = iterum([1, 2, 3, 4])
- >>> assert it.position(lambda x: x >= 2) == Some(1)
- >>> assert it.next() == Some(3)
- >>> assert it.position(lambda x: x == 4) == Some(0)
+ ```
"""
for i, x in enumerate(self):
if predicate(x):
@@ -1171,34 +1347,41 @@ def product(self: Iterum[SupportsMulT]) -> Option[SupportsMulT]:
An empty iterum returns [nil][iterum.nil].
- Examples:
+ **Examples:**
+
+ ```python
+ >>> def factorial(n: int) -> int:
+ ... return seq(1, n + 1).product().unwrap_or(1)
+ ...
+ >>> assert factorial(0) == 1
+ >>> assert factorial(1) == 1
+ >>> assert factorial(5) == 120
- >>> def factorial(n: int) -> int:
- ... return seq(1, n + 1).product().unwrap_or(1)
- ...
- >>> assert factorial(0) == 1
- >>> assert factorial(1) == 1
- >>> assert factorial(5) == 120
+ ```
"""
return self.reduce(lambda acc, x: acc * x)
def reduce(self, f: Callable[[T_co, T_co], T_co], /) -> Option[T_co]:
"""
- Reduces the elements to a single one, by repeatedly applying a reducing operation.
+ Reduces the elements to a single one, by repeatedly applying a
+ reducing operation.
If the iterum is empty, returns [nil][iterum.nil]; otherwise, returns
the result of the reduction.
- The reducing function is a closure with two arguments: an ‘accumulator’,
+ The reducing function is a closure with two arguments: an 'accumulator',
and an element. For iterums with at least one element, this is the
same as [fold()][iterum.Iterum.fold] with the first element of the
iterum as the initial accumulator value, folding every subsequent
element into it.
- Examples:
+ **Examples:**
- >>> reduced = seq(1, 10).reduce(lambda acc, e: acc + e).unwrap()
- >>> assert reduced == 45
+ ```python
+ >>> reduced = seq(1, 10).reduce(lambda acc, e: acc + e).unwrap()
+ >>> assert reduced == 45
+
+ ```
"""
first = self.next()
if first is nil:
@@ -1216,24 +1399,28 @@ def scan(self, init: U, f: Callable[[State[U], T_co], Option[V]], /) -> Scan[V]:
being the internal state and the second an iterum element.
The closure can assign to the internal state to share state between iterations.
- Examples:
-
- >>> itr = iterum([1, 2, 3, 4])
- >>> def scanner(state: State, x: int) -> Option[int]:
- ... state.value *= x
- ... if state.value > 6:
- ... return nil
- ... return Some(-state.value)
- ...
- >>> scan = itr.scan(1, scanner)
- >>> assert scan.next() == Some(-1)
- >>> assert scan.next() == Some(-2)
- >>> assert scan.next() == Some(-6)
- >>> assert scan.next() == nil
+ **Examples:**
+
+ ```python
+ >>> itr = iterum([1, 2, 3, 4])
+ >>> def scanner(state: State, x: int) -> Option[int]:
+ ... state.value *= x
+ ... if state.value > 6:
+ ... return nil
+ ... return Some(-state.value)
+ ...
+ >>> scan = itr.scan(1, scanner)
+ >>> assert scan.next() == Some(-1)
+ >>> assert scan.next() == Some(-2)
+ >>> assert scan.next() == Some(-6)
+ >>> assert scan.next() == nil
+
+ ```
"""
return Scan(self, init, f)
- # def size_hint ..., don't plan on implementing this one. Just use diterum if size is important
+ # def size_hint ..., don't plan on implementing this one. Just use diterum
+ # if size is important
def skip(self, n: int, /) -> Skip[T_co]:
"""
@@ -1244,16 +1431,23 @@ def skip(self, n: int, /) -> Skip[T_co]:
After that, all the remaining elements are yielded. In particular, if
the original iterum is too short, then the returned iterum is empty.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> itr = iterum([1, 2, 3]).skip(2)
+ >>> assert itr.next() == Some(3)
+ >>> assert itr.next() == nil
+
+ ```
- >>> itr = iterum([1, 2, 3]).skip(2)
- >>> assert itr.next() == Some(3)
- >>> assert itr.next() == nil
+ Skipping past end:
+
+ ```python
+ >>> itr = iterum([1, 2, 3]).skip(10)
+ >>> assert itr.next() == nil
+ >>> assert itr.next() == nil
- Skipping past end:
- >>> itr = iterum([1, 2, 3]).skip(10)
- >>> assert itr.next() == nil
- >>> assert itr.next() == nil
+ ```
"""
return Skip(self, n)
@@ -1265,21 +1459,28 @@ def skip_while(self, predicate: Callable[[T_co], object], /) -> SkipWhile[T_co]:
It will call this closure on each element of the iterum, and ignore
elements until it returns `False`.
- After `False` is returned, [skip_while()][iterum.Iterum.skip_while]’s
+ After `False` is returned, [skip_while()][iterum.Iterum.skip_while]'s
job is over, and the rest of the elements are yielded.
- Examples:
+ **Examples:**
- >>> itr = iterum([-1, 0, 1]).skip_while(lambda x: x < 0)
- >>> assert itr.next() == Some(0)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == nil
+ ```python
+ >>> itr = iterum([-1, 0, 1]).skip_while(lambda x: x < 0)
+ >>> assert itr.next() == Some(0)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == nil
+
+ ```
+
+ After first `False` condition is hit, no further elements are checked:
- After first `False` condition is hit, no further elements are checked:
- >>> itr = iterum([-1, 0, 1, -3]).skip_while(lambda x: x < 0)
- >>> assert itr.next() == Some(0)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(-3)
+ ```python
+ >>> itr = iterum([-1, 0, 1, -3]).skip_while(lambda x: x < 0)
+ >>> assert itr.next() == Some(0)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(-3)
+
+ ```
"""
return SkipWhile(self, predicate)
@@ -1288,13 +1489,16 @@ def step_by(self, step: int, /) -> StepBy[T_co]:
Creates an iterum starting at the same point, but stepping by the
given amount at each iteration. This always includes the first element.
- Examples:
+ **Examples:**
- >>> itr = iterum([0, 1, 2, 3, 4, 5]).step_by(2)
- >>> assert itr.next() == Some(0)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == Some(4)
- >>> assert itr.next() == nil
+ ```python
+ >>> itr = iterum([0, 1, 2, 3, 4, 5]).step_by(2)
+ >>> assert itr.next() == Some(0)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == Some(4)
+ >>> assert itr.next() == nil
+
+ ```
"""
return StepBy(self, step)
@@ -1306,17 +1510,24 @@ def sum(self: Iterum[SupportsSumNoDefaultT]) -> Option[SupportsSumNoDefaultT]:
An empty iterum returns [nil][iterum.nil].
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [1, 2, 3]
+ >>> sum_ = iterum(a).sum().unwrap_or(0)
+ >>> assert sum_ == 6
- >>> a = [1, 2, 3]
- >>> sum_ = iterum(a).sum().unwrap_or(0)
- >>> assert sum_ == 6
+ ```
- >>> sum_ = iterum([]).sum().unwrap_or(0)
- >>> assert sum_ == 0
+ ```python
+ >>> sum_ = iterum([]).sum().unwrap_or(0)
+ >>> assert sum_ == 0
+
+ ```
"""
- # NOTE: This forces users to pick a default or suffer the unwrapping consequences
- # a more reasonable interface since an implicit default isn't a thing
+ # NOTE: This forces users to pick a default or suffer the unwrapping
+ # consequences a more reasonable interface since an implicit
+ # default isn't a thing.
first = self.next()
if first is nil:
return nil
@@ -1334,33 +1545,45 @@ def take(self, n: int, /) -> Take[T_co]:
contains at least n elements, otherwise it contains all of the (fewer
than n) elements of the original iterum.
- Examples:
+ **Examples:**
- >>> a = [1, 2, 3]
- >>> itr = iterum(a).take(2)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
+ ```python
+ >>> a = [1, 2, 3]
+ >>> itr = iterum(a).take(2)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
+ ```
- >>> a = [1, 2, 3]
- >>> itr = iterum(a).take(2)
- >>> assert list(itr) == [1, 2]
- >>> assert itr.next() == nil
+ ```python
+ >>> a = [1, 2, 3]
+ >>> itr = iterum(a).take(2)
+ >>> assert list(itr) == [1, 2]
+ >>> assert itr.next() == nil
+ ```
- Truncate an infinite iterum:
- >>> itr = seq(...).take(3)
- >>> assert itr.next() == Some(0)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
+ Truncate an infinite iterum:
- Taking more than you have:
- >>> itr = iterum([1, 2]).take(5)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
+ ```python
+ >>> itr = seq(...).take(3)
+ >>> assert itr.next() == Some(0)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
+
+ ```
+
+ Taking more than you have:
+
+ ```python
+ >>> itr = iterum([1, 2]).take(5)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
+
+ ```
"""
return Take(self, n)
@@ -1372,21 +1595,28 @@ def take_while(self, predicate: Callable[[T_co], object], /) -> TakeWhile[T_co]:
It will call this closure on each element of the iterum, and yield
elements while it returns `True`.
- After `False` is returned, [take_while()][iterum.Iterum.take_while]’s
+ After `False` is returned, [take_while()][iterum.Iterum.take_while]'s
job is over, and the rest of the elements are ignored.
- Examples:
+ **Examples:**
+
+ ```python
+ >>> a = [-1, 0, 1]
+ >>> itr = iterum(a).take_while(lambda x: x < 0)
+ >>> assert itr.next() == Some(-1)
+ >>> assert itr.next() == nil
- >>> a = [-1, 0, 1]
- >>> itr = iterum(a).take_while(lambda x: x < 0)
- >>> assert itr.next() == Some(-1)
- >>> assert itr.next() == nil
+ ```
+
+ Stop after first `False`:
+
+ ```python
+ >>> a = [-1, 0, 1, -2]
+ >>> itr = iterum(a).take_while(lambda x: x < 0)
+ >>> assert itr.next() == Some(-1)
+ >>> assert itr.next() == nil
- Stop after first `False`:
- >>> a = [-1, 0, 1, -2]
- >>> itr = iterum(a).take_while(lambda x: x < 0)
- >>> assert itr.next() == Some(-1)
- >>> assert itr.next() == nil
+ ```
"""
return TakeWhile(self, predicate)
@@ -1403,29 +1633,36 @@ def try_fold(
successfully, producing a single, final value.
[try_fold()][iterum.Iterum.try_fold] takes two arguments: an initial
- value, and a closure with two arguments: an ‘accumulator’, and an
+ value, and a closure with two arguments: an 'accumulator', and an
element. The closure either returns successfully, with the value that
the accumulator should have for the next iteration, or it raises an
exception which short-circuits the iteration.
- Examples:
-
- >>> def checked_add_i8(lhs: int, rhs: int) -> int:
- ... value = lhs + rhs
- ... if -128 <= value <= 127:
- ... return value
- ... else:
- ... raise ValueError("Overflow!")
- ...
- >>> a = [1, 2, 3]
- >>> sum = iterum(a).try_fold(0, checked_add_i8)
- >>> assert sum == Some(6)
-
- short-circuit after a failure:
- >>> it = iterum([10, 20, 30, 100, 40, 50])
- >>> sum = it.try_fold(0, checked_add_i8)
- >>> assert sum == nil
- >>> assert list(it) == [40, 50]
+ **Examples:**
+
+ ```python
+ >>> def checked_add_i8(lhs: int, rhs: int) -> int:
+ ... value = lhs + rhs
+ ... if -128 <= value <= 127:
+ ... return value
+ ... else:
+ ... raise ValueError("Overflow!")
+ ...
+ >>> a = [1, 2, 3]
+ >>> sum = iterum(a).try_fold(0, checked_add_i8)
+ >>> assert sum == Some(6)
+
+ ```
+
+ short-circuit after a failure:
+
+ ```python
+ >>> it = iterum([10, 20, 30, 100, 40, 50])
+ >>> sum = it.try_fold(0, checked_add_i8)
+ >>> assert sum == nil
+ >>> assert list(it) == [40, 50]
+
+ ```
"""
acc = init
for x in self:
@@ -1445,34 +1682,29 @@ def try_fold(
# return
@overload
- def unzip(self: Iterum[tuple[U, V]], /) -> tuple[list[U], list[V]]:
- ...
+ def unzip(self: Iterum[tuple[U, V]], /) -> tuple[list[U], list[V]]: ...
@overload
def unzip(
self: Iterum[tuple[U, V]], container: type[list], /
- ) -> tuple[list[U], list[V]]:
- ...
+ ) -> tuple[list[U], list[V]]: ...
@overload
def unzip(
self: Iterum[tuple[U, V]], container: type[set], /
- ) -> tuple[set[U], set[V]]:
- ...
+ ) -> tuple[set[U], set[V]]: ...
@overload
def unzip(
self: Iterum[tuple[U, V]], container: type[tuple], /
- ) -> tuple[tuple[U, ...], tuple[V, ...]]:
- ...
+ ) -> tuple[tuple[U, ...], tuple[V, ...]]: ...
@overload
def unzip(
self: Iterum[tuple[object, object]],
container: Callable[[Iterable[object]], U],
/,
- ) -> tuple[U, U]:
- ...
+ ) -> tuple[U, U]: ...
def unzip(
self: Iterum[tuple[object, object]],
@@ -1488,19 +1720,22 @@ def unzip(
This function is, in some sense, the opposite of [zip][iterum.Iterum.zip].
- Examples:
+ **Examples:**
- >>> a = [(1, 2), (3, 4), (5, 6)]
- >>> left, right = iterum(a).unzip()
- >>> assert left == [1, 3, 5]
- >>> assert right == [2, 4, 6]
+ ```python
+ >>> a = [(1, 2), (3, 4), (5, 6)]
+ >>> left, right = iterum(a).unzip()
+ >>> assert left == [1, 3, 5]
+ >>> assert right == [2, 4, 6]
+
+ ```
"""
- left, right = map(container, zip(*self))
+ left, right = map(container, zip(*self, strict=False))
return left, right
def zip(self, other: Iterable[U], /) -> Zip[T_co, U]:
"""
- ‘Zips up’ two iterables into a single iterum of pairs.
+ 'Zips up' two iterables into a single iterum of pairs.
[zip()][iterum.Iterum.zip] returns a new iterum that will iterate over
two other iterables, returning a tuple where the first element comes
@@ -1512,44 +1747,56 @@ def zip(self, other: Iterable[U], /) -> Zip[T_co, U]:
first try to advance the first iterable at most one time and if it still
yielded an item try to advance the second iterable at most one time.
- To ‘undo’ the result of zipping up two iterables, see [unzip][iterum.Iterum.unzip].
-
- Examples:
-
- >>> a1 = [1, 2, 3]
- >>> a2 = [4, 5, 6]
- >>> itr = iterum(a1).zip(a2)
- >>> assert itr.next() == Some((1, 4))
- >>> assert itr.next() == Some((2, 5))
- >>> assert itr.next() == Some((3, 6))
- >>> assert itr.next() == nil
-
- zip smaller with larger:
- >>> inf_itr = seq(...)
- >>> foo_itr = iterum("foo")
- >>> zip_itr = foo_itr.zip(inf_itr)
- >>> assert zip_itr.next() == Some(("f", 0))
- >>> assert zip_itr.next() == Some(("o", 1))
- >>> assert zip_itr.next() == Some(("o", 2))
- >>> assert zip_itr.next() == nil
- >>> assert foo_itr.next() == nil
- >>> assert inf_itr.next() == Some(3)
-
- zip larger with smaller:
- >>> inf_itr = seq(...)
- >>> foo_itr = iterum("foo")
- >>> zip_itr = inf_itr.zip(foo_itr)
- >>> assert zip_itr.next() == Some((0, "f"))
- >>> assert zip_itr.next() == Some((1, "o"))
- >>> assert zip_itr.next() == Some((2, "o"))
- >>> assert zip_itr.next() == nil
- >>> assert foo_itr.next() == nil
- >>> assert inf_itr.next() == Some(4)
+ To 'undo' the result of zipping up two iterables, see
+ [unzip][iterum.Iterum.unzip].
+
+ **Examples:**
+
+ ```python
+ >>> a1 = [1, 2, 3]
+ >>> a2 = [4, 5, 6]
+ >>> itr = iterum(a1).zip(a2)
+ >>> assert itr.next() == Some((1, 4))
+ >>> assert itr.next() == Some((2, 5))
+ >>> assert itr.next() == Some((3, 6))
+ >>> assert itr.next() == nil
+
+ ```
+
+ zip smaller with larger:
+
+ ```python
+ >>> inf_itr = seq(...)
+ >>> foo_itr = iterum("foo")
+ >>> zip_itr = foo_itr.zip(inf_itr)
+ >>> assert zip_itr.next() == Some(("f", 0))
+ >>> assert zip_itr.next() == Some(("o", 1))
+ >>> assert zip_itr.next() == Some(("o", 2))
+ >>> assert zip_itr.next() == nil
+ >>> assert foo_itr.next() == nil
+ >>> assert inf_itr.next() == Some(3)
+
+ ```
+
+ zip larger with smaller:
+
+ ```python
+ >>> inf_itr = seq(...)
+ >>> foo_itr = iterum("foo")
+ >>> zip_itr = inf_itr.zip(foo_itr)
+ >>> assert zip_itr.next() == Some((0, "f"))
+ >>> assert zip_itr.next() == Some((1, "o"))
+ >>> assert zip_itr.next() == Some((2, "o"))
+ >>> assert zip_itr.next() == nil
+ >>> assert foo_itr.next() == nil
+ >>> assert inf_itr.next() == Some(4)
+
+ ```
"""
return Zip(self, other)
-def _try_next(itr: Iterator[T], /) -> Option[T]:
+def _try_next[T](itr: Iterator[T], /) -> Option[T]:
try:
nxt = next(itr)
except StopIteration:
@@ -1633,7 +1880,7 @@ def __init__(self, __iterable: Iterable[Iterable[T_co]], /) -> None:
class Fuse(Iterum[T_co]):
- __slots__ = ("_iter", "_fuse")
+ __slots__ = ("_fuse", "_iter")
def __init__(self, __iterable: Iterable[T_co]) -> None:
self._iter = iterum(__iterable)
@@ -1652,7 +1899,7 @@ def next(self) -> Option[T_co]:
class Inspect(Iterum[T_co]):
- __slots__ = ("_iter", "_f")
+ __slots__ = ("_f", "_iter")
def __init__(
self, __iterable: Iterable[T_co], f: Callable[[T_co], object], /
@@ -1667,7 +1914,7 @@ def next(self) -> Option[T_co]:
class Map(Iterum[T_co]):
- __slots__ = ("_iter", "_f")
+ __slots__ = ("_f", "_iter")
def __init__(self, __iterable: Iterable[U], f: Callable[[U], T_co], /) -> None:
self._iter = iterum(__iterable)
@@ -1678,7 +1925,7 @@ def next(self) -> Option[T_co]:
class MapWhile(Iterum[T_co]):
- __slots__ = ("_iter", "_predicate", "_fuse")
+ __slots__ = ("_fuse", "_iter", "_predicate")
def __init__(
self, __iterable: Iterable[U], predicate: Callable[[U], Option[T_co]], /
@@ -1730,7 +1977,7 @@ def peek(self, value: T_co) -> None: # type: ignore | reason: still need to con
@dataclass
-class State(Generic[T]):
+class State[T]:
"""
Simple class which holds some mutable state.
"""
@@ -1742,7 +1989,7 @@ class State(Generic[T]):
class Scan(Iterum[T_co]):
- __slots__ = ("_iter", "_state", "_f")
+ __slots__ = ("_f", "_iter", "_state")
def __init__(
self,
@@ -1756,7 +2003,10 @@ def __init__(
self._f = f
def next(self) -> Option[T_co]:
- return self._iter.next().map(lambda val: self._f(self._state, val)).flatten()
+ def scan(val) -> Option[T_co]:
+ return self._f(self._state, val)
+
+ return self._iter.next().map(scan).flatten()
class Skip(Iterum[T_co]):
@@ -1780,7 +2030,7 @@ def next(self) -> Option[T_co]:
class SkipWhile(Iterum[T_co]):
- __slots__ = ("_iter", "_predicate", "_fuse")
+ __slots__ = ("_fuse", "_iter", "_predicate")
def __init__(
self,
@@ -1823,7 +2073,7 @@ def next(self) -> Option[T_co]:
class Take(Iterum[T_co]):
- __slots__ = ("_iter", "_max", "_idx")
+ __slots__ = ("_idx", "_iter", "_max")
def __init__(self, __iterable: Iterable[T_co], n: int, /) -> None:
self._iter = iterum(__iterable)
@@ -1858,31 +2108,40 @@ class Zip(_IterumAdapter[tuple[U, V]]):
__slots__ = ("_iter",)
def __init__(self, __iterable: Iterable[U], other: Iterable[V], /) -> None:
- self._iter = zip(__iterable, other)
+ self._iter = zip(__iterable, other, strict=False)
class iterum(Iterum[T_co]):
"""
Implements an [Iterum][iterum.Iterum] interface from an iterable object.
- Examples:
+ **Examples:**
- >>> itr = iterum([1, 2])
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
+ ```python
+ >>> itr = iterum([1, 2])
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
- >>> itr = iterum([1, 2, 3, 4])
- >>> assert itr.fold(0, lambda acc, x: acc + x) == 10
-
- >>> x = [0, 1, 2, 3, 4]
- >>> y = (
- ... iterum(x)
- ... .map(lambda x: x**2 + 1)
- ... .filter(lambda x: x % 2)
- ... .collect()
- ... )
- >>> assert y == [1, 5, 17]
+ ```
+
+ ```python
+ >>> itr = iterum([1, 2, 3, 4])
+ >>> assert itr.fold(0, lambda acc, x: acc + x) == 10
+
+ ```
+
+ ```python
+ >>> x = [0, 1, 2, 3, 4]
+ >>> y = (
+ ... iterum(x)
+ ... .map(lambda x: x**2 + 1)
+ ... .filter(lambda x: x % 2)
+ ... .collect()
+ ... )
+ >>> assert y == [1, 5, 17]
+
+ ```
"""
__slots__ = ("_iter",)
@@ -1894,22 +2153,27 @@ def next(self) -> Option[T_co]:
"""
Returns the next value in the iterable if present, otherwise [nil][iterum.nil].
- Examples:
+ **Examples:**
+
+ ```python
+ >>> itr = iterum([1, 2])
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
- >>> itr = iterum([1, 2])
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
+ ```
"""
return _try_next(self._iter)
-def seq(*args, **kwargs):
- """
- During doctests seq is required to be in the globals
- This does so while avoiding importing seq during initial
- module load which would result in a circular import.
- """
- from iterum import seq
+if not TYPE_CHECKING:
+
+ def seq(*args, **kwargs):
+ """
+ During doctests seq is required to be in the globals
+ This does so while avoiding importing seq during initial
+ module load which would result in a circular import.
+ """
+ from iterum import seq
- return seq(*args, **kwargs)
+ return seq(*args, **kwargs)
diff --git a/iterum/_notset.py b/src/iterum/_notset.py
similarity index 100%
rename from iterum/_notset.py
rename to src/iterum/_notset.py
diff --git a/src/iterum/_option.py b/src/iterum/_option.py
new file mode 100644
index 0000000..19a7e35
--- /dev/null
+++ b/src/iterum/_option.py
@@ -0,0 +1,1497 @@
+from __future__ import annotations
+
+from collections.abc import Callable
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ NamedTuple,
+ NoReturn,
+ TypeVar,
+ overload,
+)
+
+from ._singleton import Singleton
+
+if TYPE_CHECKING:
+ from ._iterum import iterum
+
+
+T = TypeVar("T")
+U = TypeVar("U")
+V = TypeVar("V")
+
+S = TypeVar("S", bound="Some")
+O = TypeVar("O", bound="Option") # noqa: E741
+
+
+class Swap[T, U](NamedTuple):
+ """
+ Used for various 'swapping' operations on [Option][iterum.Option].
+ """
+
+ inserted: T
+ """
+ Value inserted into an option
+ """
+
+ returned: U
+ """
+ Value returned from an option
+ """
+
+
+class UnwrapNilError(RuntimeError):
+ """
+ Exception raised when [nil.unwrap][iterum.Nil.unwrap] is called.
+ """
+
+ def __init__(self, msg: str = "Attempted to unwrap nil") -> None:
+ super().__init__(msg)
+
+
+class ExpectNilError(RuntimeError):
+ """
+ Exception raised when [nil.expect][iterum.Nil.expect] is called.
+ """
+
+ def __init__(self, msg: str = "Expected some but option is nil") -> None:
+ super().__init__(msg)
+
+
+class Nil(Singleton):
+ """
+ [Nil][iterum.Nil] has no value.
+
+ **Examples:**
+
+ ```python
+ >>> x = Nil() # Type of "x" is "Nil"
+ >>> x
+ nil
+ >>> x.is_nil()
+ True
+
+ ```
+
+ [Nil][iterum.Nil] always returns the same object so just use [nil][iterum.nil]
+ instead.
+
+ ```python
+ >>> nil is Nil()
+ True
+
+ ```
+
+ Likely, the only practical use of the [Nil][iterum.Nil] class is for type
+ annotations and calls to `isinstance`.
+ """
+
+ __slots__ = ()
+
+ def __repr__(self) -> str:
+ return "nil"
+
+ def __bool__(self) -> Literal[False]:
+ return False
+
+ def and_(self, optb: Option[U], /) -> Nil:
+ """
+ Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
+ returns optb.
+
+ Arguments passed to [and_][iterum.Nil.and_] are eagerly evaluated; if
+ you are passing the result of a function call, it is recommended to use
+ [and_then][iterum.Nil.and_then], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).and_(nil) == nil
+ >>> assert nil.and_(Some("foo")) == nil
+ >>> assert Some(2).and_(Some("foo")) == Some("foo")
+ >>> assert nil.and_(nil) == nil
+
+ ```
+
+ Note: because `and` is a keyword, this method is called `and_` instead.
+ """
+ # 'and' is a keyword, so instead we use 'and_'
+ return self
+
+ def and_then(self, f: Callable[[Any], Option[U]], /) -> Nil:
+ """
+ Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
+ calls `f` with the wrapped value and returns the result.
+
+ **Examples:**
+
+ ```python
+ >>> MAX_U32 = (1 << 32) - 1
+ >>> def checked_sq_u32(x: int) -> Option[int]:
+ ... sq = x * x
+ ... if sq > MAX_U32:
+ ... return nil
+ ... return Some(sq)
+ ...
+ >>> assert Some(2).and_then(checked_sq_u32) == Some(4)
+ >>> assert Some(1_000_000).and_then(checked_sq_u32) == nil
+ >>> assert nil.and_then(checked_sq_u32) == nil
+
+ ```
+ """
+ return self
+
+ def expect(self, msg: str, /) -> NoReturn:
+ """
+ Returns the contained [Some][iterum.Some] value, consuming the self value.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some("value")
+ >>> assert x.expect("fruits are healthy") == "value"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.expect("fruits are healthy")
+ ... except ExpectNilError as ex:
+ ... print(ex)
+ ...
+ fruits are healthy
+
+ ```
+
+ Raises:
+ ExpectNilError: if the value is a [nil][iterum.nil] with a custom
+ panic message provided by msg.
+ """
+ raise ExpectNilError(msg)
+
+ def filter(self, predicate: Callable[[Any], object], /) -> Nil:
+ """
+ Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
+ calls `predicate` with the wrapped value and returns:
+
+ - [Some(value)][iterum.Some] if the predicate returns `True`
+ - [nil][iterum.nil] if the predicate returns `False`
+
+ **Examples:**
+
+ ```python
+ >>> assert nil.filter(lambda x: x % 2 == 0) == nil
+ >>> assert Some(3).filter(lambda x: x % 2 == 0) == nil
+ >>> assert Some(4).filter(lambda x: x % 2 == 0) == Some(4)
+
+ ```
+ """
+ return self
+
+ def flatten(self) -> Nil:
+ """
+ Converts from `Option[Option[T]]` to `Option[T]`.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(Some(6)).flatten() == Some(6)
+ >>> assert Some(nil).flatten() == nil
+ >>> assert nil.flatten() == nil
+
+ ```
+ """
+ return self
+
+ def get_or_insert(self, value: T, /) -> Swap[Some[T], T]:
+ """
+ Inserts value into the option if it is [nil][iterum.nil], then returns a
+ tuple of the resulting option and the returned value.
+
+ See also [insert][iterum.Nil.insert], which updates the value even if
+ the option already contains a value.
+
+ **Examples:**
+
+ ```python
+ >>> opt = nil
+ >>> opt, value = opt.get_or_insert(5)
+ >>> assert value == 5
+ >>> assert opt == Some(5)
+
+ ```
+
+ ```python
+ >>> opt = Some(3)
+ >>> opt, value = opt.get_or_insert(5)
+ >>> assert value == 3
+ >>> assert opt == Some(3)
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> assert Some(10).get_or_insert(5).returned == 10
+ >>> assert nil.get_or_insert(5).returned == 5
+
+ ```
+
+ ```python
+ >>> assert Some(10).get_or_insert(5).inserted == Some(10)
+ >>> assert nil.get_or_insert(5).inserted == Some(5)
+
+ ```
+ """
+ return Swap(Some(value), value)
+
+ def get_or_insert_with(self, f: Callable[[], T], /) -> Swap[Some[T], T]:
+ """
+ Inserts a value computed from `f` into the option if it is
+ [nil][iterum.nil], then returns a tuple of the resulting option and the
+ returned value.
+
+ **Examples:**
+
+ ```python
+ >>> opt = nil
+ >>> opt, value = opt.get_or_insert_with(lambda: 5)
+ >>> assert value == 5
+ >>> assert opt == Some(5)
+
+ ```
+
+ ```python
+ >>> opt = Some(3)
+ >>> opt, value = opt.get_or_insert_with(lambda: 5)
+ >>> assert value == 3
+ >>> assert opt == Some(3)
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(10).get_or_insert_with(lambda: 5)
+ >>> assert swap.inserted == Some(10)
+ >>> assert swap.returned == 10
+
+ ```
+
+ ```python
+ >>> swap = nil.get_or_insert_with(lambda: 5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == 5
+
+ ```
+ """
+ return Swap(Some(value := f()), value)
+
+ def insert(self, value: T, /) -> Swap[Some[T], T]:
+ """
+ Inserts value into the option, then returns a tuple of the resulting
+ option and the returned value.
+
+ If the option already contains a value, the old value is dropped.
+
+ See also [get_or_insert][iterum.Nil.get_or_insert], which doesn't
+ update the value if the option already contains a value.
+
+ **Examples:**
+
+ ```python
+ >>> opt = nil
+ >>> opt, value = opt.insert(1)
+ >>> assert value == 1
+ >>> assert opt == Some(1)
+
+ ```
+
+ ```python
+ >>> opt = Some(3)
+ >>> opt, value = opt.insert(1)
+ >>> assert value == 1
+ >>> assert opt == Some(1)
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(10).insert(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == 5
+
+ ```
+
+ ```python
+ >>> swap = nil.insert(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == 5
+
+ ```
+ """
+ return Swap(Some(value), value)
+
+ def is_nil(self) -> Literal[True]:
+ """
+ Returns `True` if the option is a [nil][iterum.nil] value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).is_nil() is False
+ >>> assert nil.is_nil() is True
+
+ ```
+ """
+ return True
+
+ def is_some(self) -> Literal[False]:
+ """
+ Returns `True` if the option is a Some value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).is_some() is True
+ >>> assert nil.is_some() is False
+
+ ```
+ """
+ return False
+
+ def is_some_and(self, f: Callable[[Any], object]) -> Literal[False]:
+ """
+ Returns `True` if the option is a [Some][iterum.Some] and the value
+ inside of it matches a predicate.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).is_some_and(lambda x: x > 1) is True
+ >>> assert Some(0).is_some_and(lambda x: x > 1) is False
+ >>> assert nil.is_some_and(lambda x: x > 1) is False
+
+ ```
+ """
+ return False
+
+ def iter(self) -> iterum[Any]:
+ """
+ Returns an iterator over the possibly contained value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(4).iter().next() == Some(4)
+ >>> assert nil.iter().next() == nil
+
+ ```
+ """
+ from ._iterum import iterum
+
+ return iterum([])
+
+ def map(self, f: Callable[[Any], Any], /) -> Nil:
+ """
+ Maps an [Option[T]][iterum.Option] to [Option[U]][iterum.Option] by
+ applying a function to a contained value (if [Some][iterum.Some]) or
+ returns [nil][iterum.nil] (if [Nil][iterum.Nil]).
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("Hello, World!").map(len) == Some(13)
+ >>> assert nil.map(len) == nil
+
+ ```
+ """
+ return self
+
+ def map_or(self, default: U, f: Callable[[Any], U], /) -> U:
+ """
+ Returns the provided default result (if [nil][iterum.nil]), or applies a
+ function to the contained value (if any).
+
+ Arguments passed to [map_or][iterum.Nil.map_or] are eagerly evaluated;
+ if you are passing the result of a function call, it is recommended to
+ use [map_or_else][iterum.Nil.map_or_else], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("foo").map_or(42, len) == 3
+ >>> assert nil.map_or(42, len) == 42
+
+ ```
+ """
+ return default
+
+ def map_or_else(self, default: Callable[[], U], f: Callable[[Any], U], /) -> U:
+ """
+ Computes a default function result (if [nil][iterum.nil]), or applies a
+ different function to the contained value (if any).
+
+ **Examples:**
+
+ ```python
+ >>> k = 21
+ >>> assert Some("foo").map_or_else(lambda: 2 * k, len) == 3
+ >>> assert nil.map_or_else(lambda: 2 * k, len) == 42
+
+ ```
+ """
+ return default()
+
+ def ok_or(self, err: Exception, /) -> NoReturn:
+ """
+ Unwraps the option returning the value if [Some][iterum.Some] or raises
+ the provided exception if [nil][iterum.nil].
+
+ Arguments passed to [ok_or][iterum.Nil.ok_or] are eagerly evaluated; if
+ you are passing the result of a function call, it is recommended to use
+ [ok_or_else][iterum.Nil.ok_or_else], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("foo").ok_or(RuntimeError("oh no!")) == "foo"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.ok_or(RuntimeError("oh no!"))
+ ... except RuntimeError as ex:
+ ... print(ex)
+ ...
+ oh no!
+
+ ```
+ """
+ raise err
+
+ def ok_or_else(self, err: Callable[[], Exception], /) -> NoReturn:
+ """
+ Unwraps the option returning the value if [Some][iterum.Some] or raises
+ the exception returned by the provided callable if [nil][iterum.nil].
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("foo").ok_or_else(AssertionError) == "foo"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.ok_or_else(lambda: AssertionError("oopsy!"))
+ ... except AssertionError as ex:
+ ... print(ex)
+ ...
+ oopsy!
+
+ ```
+ """
+ raise err()
+
+ def or_(self, optb: O, /) -> O:
+ """
+ Returns the option if it contains a value, otherwise returns optb.
+
+ Arguments passed to [or_][iterum.Nil.or_] are eagerly evaluated; if you
+ are passing the result of a function call, it is recommended to use
+ [or_else][iterum.Nil.or_else], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).or_(nil) == Some(2)
+ >>> assert nil.or_(Some(100)) == Some(100)
+ >>> assert Some(2).or_(Some(100)) == Some(2)
+ >>> assert nil.or_(nil) == nil
+
+ ```
+
+ Note: because `or` is a keyword, this method is called `or_` instead.
+ """
+ # 'or' is a keyword, so instead we use 'or_'
+ return optb
+
+ def or_else(self, f: Callable[[], O], /) -> O:
+ """
+ Returns the option if it contains a value, otherwise calls `f` and
+ returns the result.
+
+ **Examples:**
+
+ ```python
+ >>> def nobody() -> Option[str]:
+ ... return nil
+ ...
+ >>> def vikings() -> Option[str]:
+ ... return Some("vikings")
+ ...
+ >>> assert Some("barbarians").or_else(vikings) == Some("barbarians")
+ >>> assert nil.or_else(vikings) == Some("vikings")
+ >>> assert nil.or_else(nobody) == nil
+
+ ```
+ """
+ return f()
+
+ def replace(self, value: T, /) -> Swap[Some[T], Nil]:
+ """
+ Replaces the actual value in the option by the value given in parameter,
+ returning a tuple of the resulting option and the returned old value if
+ present.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some(2)
+ >>> new, old = x.replace(5)
+ >>> assert new == Some(5)
+ >>> assert old == Some(2)
+
+ ```
+
+ ```python
+ >>> x = nil
+ >>> new, old = x.replace(5)
+ >>> assert new == Some(5)
+ >>> assert old == nil
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(10).replace(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == Some(10)
+
+ ```
+
+ ```python
+ >>> swap = nil.replace(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == nil
+
+ ```
+ """
+ return Swap(Some(value), nil)
+
+ def take(self) -> Swap[Nil, Nil]:
+ """
+ Takes the value out of the option, returning a tuple of the resulting
+ nil and the old option.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some(2)
+ >>> new, old = x.take()
+ >>> assert new == nil
+ >>> assert old == Some(2)
+
+ ```
+
+ ```python
+ >>> x = nil
+ >>> new, old = x.take()
+ >>> assert new == nil
+ >>> assert old == nil
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(2).take()
+ >>> assert swap.inserted == nil
+ >>> assert swap.returned == Some(2)
+
+ ```
+
+ ```python
+ >>> swap = nil.take()
+ >>> assert swap.inserted == nil
+ >>> assert swap.returned == nil
+
+ ```
+ """
+ return Swap(nil, self)
+
+ # transpose ... without a Result concept there isn't any value
+
+ def unwrap(self) -> NoReturn:
+ """
+ Returns the contained [Some][iterum.Some] value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("air").unwrap() == "air"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.unwrap()
+ ... except UnwrapNilError as ex:
+ ... print("Attempted to unwrap a nil!")
+ ...
+ Attempted to unwrap a nil!
+
+ ```
+
+ Raises:
+ UnwrapNilError: if the value is a [nil][iterum.nil].
+ """
+ raise UnwrapNilError()
+
+ def unwrap_or(self, default: T, /) -> T:
+ """
+ Returns the contained [Some][iterum.Some] value or a provided default.
+
+ Arguments passed to [unwrap_or][iterum.Nil.unwrap_or] are eagerly
+ evaluated; if you are passing the result of a function call, it is
+ recommended to use [unwrap_or_else][iterum.Nil.unwrap_or_else], which
+ is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("car").unwrap_or("bike") == "car"
+ >>> assert nil.unwrap_or("bike") == "bike"
+
+ ```
+ """
+ return default
+
+ # In order for unwrap_or_default to be implemented we would
+ # need to know within nil what type we are supposed to have.
+ #
+ # If this was known we could come up with reasonable defaults,
+ # e.g. 0, {}, [], "", ...
+ # note: these also happen to be what constructing the type with no params gives.
+ #
+ # If I wanted to get real fancy could provide user way to register defaults
+ # for their custom types.
+
+ def unwrap_or_else(self, f: Callable[[], T], /) -> T:
+ """
+ Returns the contained [Some][iterum.Some] value or computes it from a closure.
+
+ **Examples:**
+
+ ```python
+ >>> k = 10
+ >>> assert Some(4).unwrap_or_else(lambda: 2 * k) == 4
+ >>> assert nil.unwrap_or_else(lambda: 2 * k) == 20
+
+ ```
+ """
+ return f()
+
+ def unzip(self) -> tuple[Nil, Nil]:
+ """
+ Unzips an option containing a tuple of two options.
+
+ If `self` is `Some((a, b))` this method returns `(Some(a), Some(b))`.
+ Otherwise, `(nil, nil)` is returned.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some((1, "hi")).unzip() == (Some(1), Some("hi"))
+ >>> assert nil.unzip() == (nil, nil)
+
+ ```
+ """
+ return (nil, nil)
+
+ @overload
+ def xor(self, optb: S, /) -> S: ...
+
+ @overload
+ def xor(self, optb: Nil, /) -> Nil: ...
+
+ def xor(self, optb: O, /) -> O | Nil:
+ """
+ Returns [Some][iterum.Some] if exactly one of `self`, `optb` is
+ [Some][iterum.Some], otherwise returns [nil][iterum.nil].
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).xor(nil) == Some(2)
+ >>> assert nil.xor(Some(100)) == Some(100)
+ >>> assert Some(2).xor(Some(100)) == nil
+ >>> assert nil.xor(nil) == nil
+
+ ```
+ """
+ return nil if isinstance(optb, Nil) else optb
+
+ def zip(self, other: Option[U], /) -> Nil:
+ """
+ Zips `self` with another option.
+
+ If `self` is `Some(s)` and `other` is `Some(o)`,
+ this method returns `Some((s, o))`.
+ Otherwise, [nil][iterum.nil] is returned.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(1).zip(Some("hi")) == Some((1, "hi"))
+ >>> assert Some(1).zip(nil) == nil
+ >>> assert nil.zip(nil) == nil
+
+ ```
+ """
+ return self
+
+
+nil = Nil()
+"""
+Instance of type [Nil][iterum.Nil]. See [Nil][iterum.Nil] for more details.
+"""
+
+
+class Some[T]:
+ """
+ [Some][iterum.Some] value of type T.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some(1) # Type of "x" is "Some[int]"
+ >>> x
+ Some(1)
+ >>> x.is_some()
+ True
+ >>> x.unwrap()
+ 1
+
+ ```
+ """
+
+ __match_args__ = ("_value",)
+
+ def __init__(self, value: T, /) -> None:
+ self._value = value
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Some):
+ return NotImplemented
+ return self._value == other._value
+
+ def __repr__(self) -> str:
+ return f"{Some.__name__}({self._value!r})"
+
+ def __bool__(self) -> Literal[True]:
+ return True
+
+ def and_(self, optb: O, /) -> O:
+ """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
+ returns optb.
+
+ Arguments passed to [and_][iterum.Some.and_] are eagerly evaluated; if
+ you are passing the result of a function call, it is recommended to use
+ [and_then][iterum.Some.and_then], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).and_(nil) == nil
+ >>> assert nil.and_(Some("foo")) == nil
+ >>> assert Some(2).and_(Some("foo")) == Some("foo")
+ >>> assert nil.and_(nil) == nil
+
+ ```
+
+ Note: because `and` is a keyword, this method is called `and_` instead.
+ """
+ return optb
+
+ def and_then(self, f: Callable[[T], O], /) -> O:
+ """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
+ calls `f` with the wrapped value and returns the result.
+
+ **Examples:**
+
+ ```python
+ >>> MAX_U32 = (1 << 32) - 1
+ >>> def checked_sq_u32(x: int) -> Option[int]:
+ ... sq = x * x
+ ... if sq > MAX_U32:
+ ... return nil
+ ... return Some(sq)
+ ...
+ >>> assert Some(2).and_then(checked_sq_u32) == Some(4)
+ >>> assert Some(1_000_000).and_then(checked_sq_u32) == nil
+ >>> assert nil.and_then(checked_sq_u32) == nil
+
+ ```
+ """
+ return f(self._value)
+
+ def expect(self, msg: str, /) -> T:
+ """Returns the contained [Some][iterum.Some] value, consuming the self value.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some("value")
+ >>> assert x.expect("fruits are healthy") == "value"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.expect("fruits are healthy")
+ ... except ExpectNilError as ex:
+ ... print(ex)
+ ...
+ fruits are healthy
+
+ ```
+
+ Raises:
+ ExpectNilError: if the value is a [nil][iterum.nil] with a custom
+ message provided by msg.
+ """
+ return self._value
+
+ def filter(self, predicate: Callable[[T], object], /) -> Option[T]:
+ """Returns [nil][iterum.nil] if the option is [nil][iterum.nil], otherwise
+ calls `predicate` with the wrapped value and returns:
+
+ - [Some(value)][iterum.Some] if the predicate returns `True`
+ - [nil][iterum.nil] if the predicate returns `False`
+
+ **Examples:**
+
+ ```python
+ >>> assert nil.filter(lambda x: x % 2 == 0) == nil
+ >>> assert Some(3).filter(lambda x: x % 2 == 0) == nil
+ >>> assert Some(4).filter(lambda x: x % 2 == 0) == Some(4)
+
+ ```
+ """
+ return self if predicate(self._value) else Nil()
+
+ def flatten(self: Some[O]) -> O:
+ """Converts from `Option[Option[T]]` to `Option[T]`.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(Some(6)).flatten() == Some(6)
+ >>> assert Some(nil).flatten() == nil
+ >>> assert nil.flatten() == nil
+
+ ```
+ """
+ if isinstance(self._value, (Some, Nil)):
+ return self._value
+ else:
+ raise TypeError(f"Cannot flatten type: Some({type(self._value).__name__})")
+
+ def get_or_insert(self, value: T, /) -> Swap[Some[T], T]:
+ """Inserts value into the option if it is [nil][iterum.nil], then returns a
+ tuple of the resulting option and the returned value.
+
+ See also [insert][iterum.Some.insert], which updates the value even if
+ the option already contains a value.
+
+ **Examples:**
+
+ ```python
+ >>> opt = nil
+ >>> opt, value = opt.get_or_insert(5)
+ >>> assert value == 5
+ >>> assert opt == Some(5)
+
+ ```
+
+ ```python
+ >>> opt = Some(3)
+ >>> opt, value = opt.get_or_insert(5)
+ >>> assert value == 3
+ >>> assert opt == Some(3)
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> assert Some(10).get_or_insert(5).returned == 10
+ >>> assert nil.get_or_insert(5).returned == 5
+
+ ```
+
+ ```python
+ >>> assert Some(10).get_or_insert(5).inserted == Some(10)
+ >>> assert nil.get_or_insert(5).inserted == Some(5)
+
+ ```
+ """
+ return Swap(Some(self._value), self._value)
+
+ def get_or_insert_with(self, f: Callable[[], T], /) -> Swap[Some[T], T]:
+ """Inserts a value computed from `f` into the option if it is
+ [nil][iterum.nil], then returns a tuple of the resulting option and the
+ returned value.
+
+ **Examples:**
+
+ ```python
+ >>> opt = nil
+ >>> opt, value = opt.get_or_insert_with(lambda: 5)
+ >>> assert value == 5
+ >>> assert opt == Some(5)
+
+ ```
+
+ ```python
+ >>> opt = Some(3)
+ >>> opt, value = opt.get_or_insert_with(lambda: 5)
+ >>> assert value == 3
+ >>> assert opt == Some(3)
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(10).get_or_insert_with(lambda: 5)
+ >>> assert swap.inserted == Some(10)
+ >>> assert swap.returned == 10
+
+ ```
+
+ ```python
+ >>> swap = nil.get_or_insert_with(lambda: 5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == 5
+
+ ```
+ """
+ return Swap(Some(self._value), self._value)
+
+ def insert(self, value: T, /) -> Swap[Some[T], T]:
+ """Inserts value into the option, then returns a tuple of the resulting
+ option and the returned value.
+
+ If the option already contains a value, the old value is dropped.
+
+ See also [get_or_insert][iterum.Some.get_or_insert], which doesn't
+ update the value if the option already contains a value.
+
+ **Examples:**
+
+ ```python
+ >>> opt = nil
+ >>> opt, value = opt.insert(1)
+ >>> assert value == 1
+ >>> assert opt == Some(1)
+
+ ```
+
+ ```python
+ >>> opt = Some(3)
+ >>> opt, value = opt.insert(1)
+ >>> assert value == 1
+ >>> assert opt == Some(1)
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(10).insert(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == 5
+
+ ```
+
+ ```python
+ >>> swap = nil.insert(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == 5
+
+ ```
+ """
+ self._value = value
+ return Swap(Some(self._value), self._value)
+
+ def is_nil(self) -> Literal[False]:
+ """Returns `True` if the option is a [nil][iterum.nil] value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).is_nil() is False
+ >>> assert nil.is_nil() is True
+
+ ```
+ """
+ return False
+
+ def is_some(self) -> Literal[True]:
+ """Returns `True` if the option is a Some value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).is_some() is True
+ >>> assert nil.is_some() is False
+
+ ```
+ """
+ return True
+
+ def is_some_and(self, f: Callable[[T], object]) -> bool:
+ """Returns `True` if the option is a [Some][iterum.Some] and the value
+ inside of it matches a predicate.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).is_some_and(lambda x: x > 1) is True
+ >>> assert Some(0).is_some_and(lambda x: x > 1) is False
+ >>> assert nil.is_some_and(lambda x: x > 1) is False
+
+ ```
+ """
+ return bool(f(self.unwrap()))
+
+ def iter(self) -> iterum[T]:
+ """Returns an iterator over the possibly contained value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(4).iter().next() == Some(4)
+ >>> assert nil.iter().next() == nil
+
+ ```
+ """
+ from ._iterum import iterum
+
+ return iterum([self._value])
+
+ def map(self, f: Callable[[T], U], /) -> Some[U]:
+ """Maps an [Option[T]][iterum.Option] to [Option[U]][iterum.Option] by
+ applying a function to a contained value (if [Some][iterum.Some]) or
+ returns [nil][iterum.nil] (if [Nil][iterum.Nil]).
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("Hello, World!").map(len) == Some(13)
+ >>> assert nil.map(len) == nil
+
+ ```
+ """
+ return Some(f(self._value))
+
+ def map_or(self, default: U, f: Callable[[T], U], /) -> U:
+ """
+ Returns the provided default result (if [nil][iterum.nil]), or applies a
+ function to the contained value (if any).
+
+ Arguments passed to [map_or][iterum.Some.map_or] are eagerly evaluated;
+ if you are passing the result of a function call, it is recommended to
+ use [map_or_else][iterum.Some.map_or_else], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("foo").map_or(42, len) == 3
+ >>> assert nil.map_or(42, len) == 42
+
+ ```
+ """
+ return f(self._value)
+
+ def map_or_else(self, default: Callable[[], U], f: Callable[[T], U], /) -> U:
+ """
+ Computes a default function result (if [nil][iterum.nil]), or applies a
+ different function to the contained value (if any).
+
+ **Examples:**
+
+ ```python
+ >>> k = 21
+ >>> assert Some("foo").map_or_else(lambda: 2 * k, len) == 3
+ >>> assert nil.map_or_else(lambda: 2 * k, len) == 42
+
+ ```
+ """
+ return f(self._value)
+
+ def ok_or(self, err: Exception, /) -> T:
+ """Unwraps the option returning the value if [Some][iterum.Some] or raises
+ the provided exception if [nil][iterum.nil].
+
+ Arguments passed to [ok_or][iterum.Some.ok_or] are eagerly evaluated; if
+ you are passing the result of a function call, it is recommended to use
+ [ok_or_else][iterum.Some.ok_or_else], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("foo").ok_or(RuntimeError("oh no!")) == "foo"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.ok_or(RuntimeError("oh no!"))
+ ... except RuntimeError as ex:
+ ... print(ex)
+ ...
+ oh no!
+
+ ```
+ """
+ return self._value
+
+ def ok_or_else(self, err: Callable[[], Exception], /) -> T:
+ """Unwraps the option returning the value if [Some][iterum.Some] or raises
+ the exception returned by the provided callable if [nil][iterum.nil].
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("foo").ok_or_else(AssertionError) == "foo"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.ok_or_else(lambda: AssertionError("oopsy!"))
+ ... except AssertionError as ex:
+ ... print(ex)
+ ...
+ oopsy!
+
+ ```
+ """
+ return self._value
+
+ def or_(self, optb: Option[T], /) -> Some[T]:
+ """Returns the option if it contains a value, otherwise returns optb.
+
+ Arguments passed to [or_][iterum.Some.or_] are eagerly evaluated; if you
+ are passing the result of a function call, it is recommended to use
+ [or_else][iterum.Some.or_else], which is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).or_(nil) == Some(2)
+ >>> assert nil.or_(Some(100)) == Some(100)
+ >>> assert Some(2).or_(Some(100)) == Some(2)
+ >>> assert nil.or_(nil) == nil
+
+ ```
+
+ Note: because `or` is a keyword, this method is called `or_` instead.
+ """
+ # 'or' is a keyword, so instead we use 'or_'
+ return self
+
+ def or_else(self, f: Callable[[], Option[T]], /) -> Some[T]:
+ """Returns the option if it contains a value, otherwise calls `f` and
+ returns the result.
+
+ **Examples:**
+
+ ```python
+ >>> def nobody() -> Option[str]:
+ ... return nil
+ ...
+ >>> def vikings() -> Option[str]:
+ ... return Some("vikings")
+ ...
+ >>> assert Some("barbarians").or_else(vikings) == Some("barbarians")
+ >>> assert nil.or_else(vikings) == Some("vikings")
+ >>> assert nil.or_else(nobody) == nil
+
+ ```
+ """
+ return self
+
+ def replace(self, value: T, /) -> Swap[Some[T], Some[T]]:
+ """Replaces the actual value in the option by the value given in parameter,
+ returning a tuple of the resulting option and the returned old value if
+ present.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some(2)
+ >>> new, old = x.replace(5)
+ >>> assert new == Some(5)
+ >>> assert old == Some(2)
+
+ ```
+
+ ```python
+ >>> x = nil
+ >>> new, old = x.replace(5)
+ >>> assert new == Some(5)
+ >>> assert old == nil
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(10).replace(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == Some(10)
+
+ ```
+
+ ```python
+ >>> swap = nil.replace(5)
+ >>> assert swap.inserted == Some(5)
+ >>> assert swap.returned == nil
+
+ ```
+ """
+ old = self._value
+ self._value = value
+ return Swap(Some(self._value), Some(old))
+
+ def take(self) -> Swap[Nil, Some[T]]:
+ """Takes the value out of the option, returning a tuple of the resulting
+ nil and the old option.
+
+ **Examples:**
+
+ ```python
+ >>> x = Some(2)
+ >>> new, old = x.take()
+ >>> assert new == nil
+ >>> assert old == Some(2)
+
+ ```
+
+ ```python
+ >>> x = nil
+ >>> new, old = x.take()
+ >>> assert new == nil
+ >>> assert old == nil
+
+ ```
+
+ Alternatively, access the named attributes of [Swap][iterum.Swap],
+ [inserted][iterum.Swap.inserted] and [returned][iterum.Swap.returned]:
+
+ ```python
+ >>> swap = Some(2).take()
+ >>> assert swap.inserted == nil
+ >>> assert swap.returned == Some(2)
+
+ ```
+
+ ```python
+ >>> swap = nil.take()
+ >>> assert swap.inserted == nil
+ >>> assert swap.returned == nil
+
+ ```
+ """
+ return Swap(nil, self)
+
+ # transpose ... without a Result concept there isn't any value
+
+ def unwrap(self) -> T:
+ """Returns the contained [Some][iterum.Some] value.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("air").unwrap() == "air"
+
+ ```
+
+ ```python
+ >>> try:
+ ... nil.unwrap()
+ ... except UnwrapNilError as ex:
+ ... print("Attempted to unwrap a nil!")
+ ...
+ Attempted to unwrap a nil!
+
+ ```
+
+ Raises:
+ UnwrapNilError: if the value is a [nil][iterum.nil].
+ """
+ return self._value
+
+ def unwrap_or(self, default: T, /) -> T:
+ """
+ Returns the contained [Some][iterum.Some] value or a provided default.
+
+ Arguments passed to [unwrap_or][iterum.Some.unwrap_or] are eagerly
+ evaluated; if you are passing the result of a function call, it is
+ recommended to use [unwrap_or_else][iterum.Some.unwrap_or_else], which
+ is lazily evaluated.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some("car").unwrap_or("bike") == "car"
+ >>> assert nil.unwrap_or("bike") == "bike"
+
+ ```
+ """
+ return self._value
+
+ def unwrap_or_else(self, f: Callable[[], T], /) -> T:
+ """
+ Returns the contained [Some][iterum.Some] value or computes it from a
+ closure.
+
+ **Examples:**
+
+ ```python
+ >>> k = 10
+ >>> assert Some(4).unwrap_or_else(lambda: 2 * k) == 4
+ >>> assert nil.unwrap_or_else(lambda: 2 * k) == 20
+
+ ```
+ """
+ return self._value
+
+ def unzip(self: Some[tuple[U, V]]) -> tuple[Some[U], Some[V]]:
+ """Unzips an option containing a tuple of two options.
+
+ If `self` is `Some((a, b))` this method returns `(Some(a), Some(b))`.
+ Otherwise, `(nil, nil)` is returned.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some((1, "hi")).unzip() == (Some(1), Some("hi"))
+ >>> assert nil.unzip() == (nil, nil)
+
+ ```
+ """
+ left, right = self._value
+ return Some(left), Some(right)
+
+ @overload
+ def xor(self, optb: Some[T], /) -> Nil: ...
+
+ @overload
+ def xor(self, optb: Nil, /) -> Some[T]: ...
+
+ def xor(self, optb: Option[T], /) -> Option[T]:
+ """
+ Returns [Some][iterum.Some] if exactly one of `self`, `optb` is
+ [Some][iterum.Some], otherwise returns [nil][iterum.nil].
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(2).xor(nil) == Some(2)
+ >>> assert nil.xor(Some(100)) == Some(100)
+ >>> assert Some(2).xor(Some(100)) == nil
+ >>> assert nil.xor(nil) == nil
+
+ ```
+ """
+ return self if isinstance(optb, Nil) else nil
+
+ @overload
+ def zip(self, other: Some[U], /) -> Some[tuple[T, U]]: ...
+
+ @overload
+ def zip(self, other: Nil, /) -> Nil: ...
+
+ def zip(self, other: Option[U], /) -> Option[tuple[T, U]]:
+ """
+ Zips `self` with another option.
+
+ If `self` is `Some(s)` and `other` is `Some(o)`,
+ this method returns `Some((s, o))`.
+ Otherwise, [nil][iterum.nil] is returned.
+
+ **Examples:**
+
+ ```python
+ >>> assert Some(1).zip(Some("hi")) == Some((1, "hi"))
+ >>> assert Some(1).zip(nil) == nil
+ >>> assert nil.zip(nil) == nil
+
+ ```
+ """
+ return nil if isinstance(other, Nil) else Some((self._value, other._value))
+
+
+type Option[T] = "Some[T] | Nil"
+"""
+Type alias representing something which is either of type
+[Some][iterum.Some] or [Nil][iterum.Nil].
+
+**Examples:**
+
+Type annotate a function which returns `Some[int]` or `nil`:
+
+```python
+>>> def checked_div(num: int, dem: int) -> Option[int]:
+... try:
+... return Some(num // dem)
+... except ZeroDivisionError:
+... return nil
+...
+
+```
+
+Use `isinstance` to narrow the type:
+
+```python
+>>> x = checked_div(10, 3)
+>>> reveal_type(x) # Type of "x" is "Some[int] | Nil"
+>>> if isinstance(x, Some):
+... reveal_type(x) # Type of "x" is "Some[int]"
+... else:
+... reveal_type(x) # Type of "x" is "Nil"
+...
+
+```
+
+Alternatively use pattern matching:
+
+```python
+>>> match x:
+... case Some(value):
+... print(f"Result: {value=}")
+... case Nil:
+... print("Cannot divide by 0")
+...
+
+```
+"""
diff --git a/iterum/_ordering.py b/src/iterum/_ordering.py
similarity index 63%
rename from iterum/_ordering.py
rename to src/iterum/_ordering.py
index 0374713..42be869 100644
--- a/iterum/_ordering.py
+++ b/src/iterum/_ordering.py
@@ -1,7 +1,6 @@
from __future__ import annotations
-from enum import Enum
-from enum import unique
+from enum import Enum, unique
from ._singleton import create_singleton
@@ -35,20 +34,27 @@ def cmp(lhs, rhs, /) -> Ordering:
Examples:
- >>> Ordering.cmp(1, 2)
- Ordering.Less
- >>> Ordering.cmp(1, 1)
- Ordering.Equal
- >>> Ordering.cmp(2, 1)
- Ordering.Greater
-
- A `TypeError` will be raised if the two objects are not comparable:
- >>> try:
- ... Ordering.cmp(1, "two")
- ... except TypeError as ex:
- ... print(f"exception received: {ex}")
- ...
- exception received: '>' not supported between instances of 'int' and 'str'
+ ```python
+ >>> Ordering.cmp(1, 2)
+ Ordering.Less
+ >>> Ordering.cmp(1, 1)
+ Ordering.Equal
+ >>> Ordering.cmp(2, 1)
+ Ordering.Greater
+
+ ```
+
+ A `TypeError` will be raised if the two objects are not comparable:
+
+ ```python
+ >>> try:
+ ... Ordering.cmp(1, "two")
+ ... except TypeError as ex:
+ ... print(f"exception received: {ex}")
+ ...
+ exception received: '>' not supported between instances of 'int' and 'str'
+
+ ```
"""
if lhs == rhs:
diff --git a/iterum/_seq.py b/src/iterum/_seq.py
similarity index 71%
rename from iterum/_seq.py
rename to src/iterum/_seq.py
index 227fed7..85d9669 100644
--- a/iterum/_seq.py
+++ b/src/iterum/_seq.py
@@ -2,17 +2,12 @@
import operator
from types import EllipsisType
-from typing import Literal
-from typing import overload
-from typing import SupportsIndex
+from typing import Literal, SupportsIndex, overload
from ._diterum import Diterum
from ._iterum import Iterum
-from ._notset import NotSet
-from ._notset import NotSetType
-from ._option import nil
-from ._option import Option
-from ._option import Some
+from ._notset import NotSet, NotSetType
+from ._option import Option, Some, nil
@overload
@@ -21,8 +16,7 @@ def seq(
end: SupportsIndex,
/,
step: SupportsIndex = 1,
-) -> Seq:
- ...
+) -> Seq: ...
@overload
@@ -31,8 +25,7 @@ def seq(
end: EllipsisType,
/,
step: SupportsIndex = 1,
-) -> InfSeq:
- ...
+) -> InfSeq: ...
@overload
@@ -41,8 +34,7 @@ def seq(
/,
*,
step: SupportsIndex = 1,
-) -> Seq:
- ...
+) -> Seq: ...
@overload
@@ -51,8 +43,7 @@ def seq(
/,
*,
step: SupportsIndex = 1,
-) -> InfSeq:
- ...
+) -> InfSeq: ...
def seq(
@@ -69,39 +60,60 @@ def seq(
If an infinite end is provided (using ellipsis `...`), an instance of
[InfSeq][iterum.InfSeq] is returned.
- Examples:
-
- >>> itr = seq(3)
- >>> assert itr.next() == Some(0)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> assert itr.next() == nil
-
- Can also specify a start and step:
- >>> itr = seq(3, 9, 3)
- >>> assert itr.next() == Some(3)
- >>> assert itr.next() == Some(6)
- >>> assert itr.next() == nil
-
- Finite ranges implement [Diterum][iterum.Diterum]:
- >>> itr = seq(3)
- >>> assert itr.len() == 3
- >>> assert itr.next_back() == Some(2)
- >>> assert itr.next() == Some(0)
-
- Specify an infinite range using `...`:
- >>> itr = seq(...)
- >>> assert itr.next() == Some(0)
- >>> assert itr.next() == Some(1)
- >>> assert itr.next() == Some(2)
- >>> # will continue forever!
-
- Similarly a start and step can be specified:
- >>> itr = seq(-10, ..., -1)
- >>> assert itr.next() == Some(-10)
- >>> assert itr.next() == Some(-11)
- >>> assert itr.next() == Some(-12)
- >>> # will continue forever!
+ **Examples:**
+
+ ```python
+ >>> itr = seq(3)
+ >>> assert itr.next() == Some(0)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> assert itr.next() == nil
+
+
+ ```
+
+ Can also specify a start and step:
+
+ ```python
+ >>> itr = seq(3, 9, 3)
+ >>> assert itr.next() == Some(3)
+ >>> assert itr.next() == Some(6)
+ >>> assert itr.next() == nil
+
+ ```
+
+ Finite ranges implement [Diterum][iterum.Diterum]:
+
+ ```python
+ >>> itr = seq(3)
+ >>> assert itr.len() == 3
+ >>> assert itr.next_back() == Some(2)
+ >>> assert itr.next() == Some(0)
+
+
+ ```
+
+ Specify an infinite range using `...`:
+
+ ```python
+ >>> itr = seq(...)
+ >>> assert itr.next() == Some(0)
+ >>> assert itr.next() == Some(1)
+ >>> assert itr.next() == Some(2)
+ >>> # will continue forever!
+
+ ```
+
+ Similarly a start and step can be specified:
+
+ ```python
+ >>> itr = seq(-10, ..., -1)
+ >>> assert itr.next() == Some(-10)
+ >>> assert itr.next() == Some(-11)
+ >>> assert itr.next() == Some(-12)
+ >>> # will continue forever!
+
+ ```
"""
if isinstance(end, NotSetType):
@@ -132,7 +144,7 @@ def _sign(step: int) -> Literal[1, 0, -1]:
class Seq(Diterum[int]):
- __slots__ = ("_front", "_back", "_step", "_dir")
+ __slots__ = ("_back", "_dir", "_front", "_step")
def __init__(self, *, start: int, end: int, step: int) -> None:
self._front = start
diff --git a/iterum/_singleton.py b/src/iterum/_singleton.py
similarity index 72%
rename from iterum/_singleton.py
rename to src/iterum/_singleton.py
index 22a6b0c..e23701d 100644
--- a/iterum/_singleton.py
+++ b/src/iterum/_singleton.py
@@ -1,16 +1,13 @@
from __future__ import annotations
-from typing import TypeVar
-
-
-Self = TypeVar("Self", bound="Singleton")
+from typing import Self
class Singleton:
__slots__ = ()
__instance = None
- def __new__(cls: type[Self]) -> Self:
+ def __new__(cls) -> Self:
if cls.__instance is None:
cls.__instance = super().__new__(cls)
return cls.__instance
diff --git a/iterum/_type_helpers.py b/src/iterum/_type_helpers.py
similarity index 65%
rename from iterum/_type_helpers.py
rename to src/iterum/_type_helpers.py
index 8ba84b7..399888a 100644
--- a/iterum/_type_helpers.py
+++ b/src/iterum/_type_helpers.py
@@ -1,15 +1,9 @@
from __future__ import annotations
-from typing import Any
-from typing import Protocol
-from typing import TYPE_CHECKING
-from typing import TypeVar
-
+from typing import TYPE_CHECKING, Any, Protocol, TypeVar
if TYPE_CHECKING:
- from _typeshed import SupportsRichComparison
- from _typeshed import SupportsRAdd
- from _typeshed import SupportsAdd
+ from _typeshed import SupportsAdd, SupportsRAdd, SupportsRichComparison
T = TypeVar("T")
@@ -23,15 +17,13 @@
)
class SupportsMul(Protocol[T_contra, T_co]):
- def __mul__(self, __x: T_contra) -> T_co:
- ...
+ def __mul__(self, __x: T_contra) -> T_co: ...
SupportsMulT = TypeVar("SupportsMulT", bound=SupportsMul)
class SupportsSumWithNoDefaultGiven(
SupportsAdd[Any, Any], SupportsRAdd[int, Any], Protocol
- ):
- ...
+ ): ...
SupportsSumNoDefaultT = TypeVar(
"SupportsSumNoDefaultT", bound=SupportsSumWithNoDefaultGiven
diff --git a/iterum/py.typed b/src/iterum/py.typed
similarity index 100%
rename from iterum/py.typed
rename to src/iterum/py.typed
diff --git a/tests/test_diterum.py b/tests/test_diterum.py
index 494175e..e393f79 100644
--- a/tests/test_diterum.py
+++ b/tests/test_diterum.py
@@ -1,6 +1,4 @@
-from iterum import diterum
-from iterum import nil
-from iterum import Some
+from iterum import Some, diterum, nil
def test_next_back_basic_usage():
diff --git a/tests/test_iterum.py b/tests/test_iterum.py
index 7c60d8c..3cde6f1 100644
--- a/tests/test_iterum.py
+++ b/tests/test_iterum.py
@@ -1,17 +1,11 @@
from __future__ import annotations
+from collections.abc import Iterator
from functools import partial
-from typing import Iterator
import pytest
-from iterum import iterum
-from iterum import nil
-from iterum import Option
-from iterum import Ordering
-from iterum import seq
-from iterum import Some
-from iterum import State
+from iterum import Option, Ordering, Some, State, iterum, nil, seq
def test_all_basic_usage():
diff --git a/tests/test_option.py b/tests/test_option.py
index 68b272f..202172f 100644
--- a/tests/test_option.py
+++ b/tests/test_option.py
@@ -2,11 +2,7 @@
import pytest
-from iterum import ExpectNilError
-from iterum import nil
-from iterum import Option
-from iterum import Some
-from iterum import UnwrapNilError
+from iterum import ExpectNilError, Option, Some, UnwrapNilError, nil
def test_eq():
@@ -15,7 +11,7 @@ def test_eq():
def test_repr():
- assert repr(Some("test")) == f"{Some.__name__}({repr('test')})"
+ assert repr(Some("test")) == f"{Some.__name__}({'test'!r})"
def test_and_basic_usage():
diff --git a/tests/test_seq.py b/tests/test_seq.py
index 6e99fd7..c27d0f8 100644
--- a/tests/test_seq.py
+++ b/tests/test_seq.py
@@ -1,8 +1,6 @@
import pytest
-from iterum import nil
-from iterum import seq
-from iterum import Some
+from iterum import Some, nil, seq
from iterum._seq import _compute_back
diff --git a/type_tests/option_helpers.py b/type_tests/option_helpers.py
index 1b6e80a..ded5e15 100644
--- a/type_tests/option_helpers.py
+++ b/type_tests/option_helpers.py
@@ -1,45 +1,33 @@
from __future__ import annotations
-from iterum import Nil
-from iterum import Option
-from iterum import Some
+from iterum import Nil, Option, Some
-def create_option() -> Option[int]:
- ...
+def create_option() -> Option[int]: ...
-def create_some() -> Some[int]:
- ...
+def create_some() -> Some[int]: ...
-def create_nil() -> Nil:
- ...
+def create_nil() -> Nil: ...
-def create_value() -> int:
- ...
+def create_value() -> int: ...
-def map_value_to_option(x: int) -> Option[str]:
- ...
+def map_value_to_option(x: int) -> Option[str]: ...
-def map_value_to_some(x: int) -> Some[str]:
- ...
+def map_value_to_some(x: int) -> Some[str]: ...
-def map_value_to_nil(x: int) -> Nil:
- ...
+def map_value_to_nil(x: int) -> Nil: ...
-def map_value_to_value(x: int) -> str:
- ...
+def map_value_to_value(x: int) -> str: ...
-def map_to_value() -> str:
- ...
+def map_to_value() -> str: ...
-def predicate(x: int) -> bool:
- ...
+def predicate(x: int) -> bool: ...
diff --git a/type_tests/type_iter.py b/type_tests/type_iter.py
index c38c6d7..e418f58 100644
--- a/type_tests/type_iter.py
+++ b/type_tests/type_iter.py
@@ -1,55 +1,51 @@
from __future__ import annotations
-from typing import assert_type
-from typing import Generic
-from typing import Iterable
-from typing import TypeVar
-
-from iterum import Chain
-from iterum import Cycle
-from iterum import Enumerate
-from iterum import Filter
-from iterum import FilterMap
-from iterum import FlatMap
-from iterum import Flatten
-from iterum import Fuse
-from iterum import Inspect
-from iterum import iterum
-from iterum import Map
-from iterum import MapWhile
-from iterum import Nil
-from iterum import nil
-from iterum import Option
-from iterum import Ordering
-from iterum import Peekable
-from iterum import Scan
-from iterum import Skip
-from iterum import SkipWhile
-from iterum import Some
-from iterum import State
-from iterum import StepBy
-from iterum import Take
-from iterum import TakeWhile
-from iterum import Zip
+from collections.abc import Iterable
+from typing import TypeVar, assert_type
+
+from iterum import (
+ Chain,
+ Cycle,
+ Enumerate,
+ Filter,
+ FilterMap,
+ FlatMap,
+ Flatten,
+ Fuse,
+ Inspect,
+ Map,
+ MapWhile,
+ Nil,
+ Option,
+ Ordering,
+ Peekable,
+ Scan,
+ Skip,
+ SkipWhile,
+ Some,
+ State,
+ StepBy,
+ Take,
+ TakeWhile,
+ Zip,
+ iterum,
+ nil,
+)
T = TypeVar("T")
-class MyCollection(Generic[T]):
- def __init__(self, _: Iterable[T]) -> None:
- ...
+class MyCollection[T]:
+ def __init__(self, _: Iterable[T]) -> None: ...
-def create_int() -> int:
- ...
+def create_int() -> int: ...
-def create_tuple_int() -> tuple[int, ...]:
- ...
+def create_tuple_int() -> tuple[int, ...]: ...
-def create_tuple_str_int() -> tuple[str, int]:
- ...
+def create_tuple_str_int() -> tuple[str, int]: ...
itr = iterum([1, 2, 3])
@@ -245,7 +241,8 @@ def iter_partition():
)
# Coulde be MyCollection[int] or MyCollection[Unkown]
# assert_type(
- # itr.partition(lambda _: False, MyCollection), tuple[MyCollection, MyCollection]
+ # itr.partition(lambda _: False, MyCollection),
+ # tuple[MyCollection, MyCollection],
# )
diff --git a/type_tests/type_nil.py b/type_tests/type_nil.py
index 45fbe86..142bb73 100644
--- a/type_tests/type_nil.py
+++ b/type_tests/type_nil.py
@@ -1,24 +1,19 @@
from __future__ import annotations
-from typing import assert_type
-from typing import Literal
-from typing import NoReturn
-from typing import TypeVar
-
-from .option_helpers import create_nil
-from .option_helpers import create_option
-from .option_helpers import create_some
-from .option_helpers import create_value
-from .option_helpers import map_to_value
-from .option_helpers import map_value_to_option
-from .option_helpers import map_value_to_value
-from .option_helpers import predicate
-from iterum import iterum
-from iterum import Nil
-from iterum import nil
-from iterum import Option
-from iterum import Some
-from iterum import Swap
+from typing import Literal, NoReturn, TypeVar, assert_type
+
+from iterum import Nil, Option, Some, Swap, iterum, nil
+
+from .option_helpers import (
+ create_nil,
+ create_option,
+ create_some,
+ create_value,
+ map_to_value,
+ map_value_to_option,
+ map_value_to_value,
+ predicate,
+)
T = TypeVar("T")
U = TypeVar("U")
diff --git a/type_tests/type_option.py b/type_tests/type_option.py
index 5e1678d..42b08c9 100644
--- a/type_tests/type_option.py
+++ b/type_tests/type_option.py
@@ -1,26 +1,21 @@
from __future__ import annotations
-from typing import Any
-from typing import assert_type
-from typing import TypeVar
-
-from .option_helpers import create_nil
-from .option_helpers import create_option
-from .option_helpers import create_some
-from .option_helpers import create_value
-from .option_helpers import map_to_value
-from .option_helpers import map_value_to_nil
-from .option_helpers import map_value_to_option
-from .option_helpers import map_value_to_some
-from .option_helpers import map_value_to_value
-from .option_helpers import predicate
-from iterum import iterum
-from iterum import Nil
-from iterum import nil
-from iterum import Option
-from iterum import Some
-from iterum import Swap
-
+from typing import Any, TypeVar, assert_type
+
+from iterum import Nil, Option, Some, Swap, iterum, nil
+
+from .option_helpers import (
+ create_nil,
+ create_option,
+ create_some,
+ create_value,
+ map_to_value,
+ map_value_to_nil,
+ map_value_to_option,
+ map_value_to_some,
+ map_value_to_value,
+ predicate,
+)
T = TypeVar("T")
U = TypeVar("U")
@@ -128,7 +123,8 @@ def option_either_else():
def option_replace():
- # assert_type(option.replace(1), Swap[Some[int], Option[int]]) # sad this doesn't work...
+ # sad this doesn't work...
+ # assert_type(option.replace(1), Swap[Some[int], Option[int]])
assert_type(option.replace(1), Swap[Some[int], Some[int]] | Swap[Some[int], Nil])
@@ -145,8 +141,7 @@ def option_unwrap_or_else():
assert_type(option.unwrap_or_else(create_value), int)
-def create_option_tuple() -> Option[tuple[int, str]]:
- ...
+def create_option_tuple() -> Option[tuple[int, str]]: ...
def option_unzip():
diff --git a/type_tests/type_seq.py b/type_tests/type_seq.py
index 5195e79..13924bc 100644
--- a/type_tests/type_seq.py
+++ b/type_tests/type_seq.py
@@ -2,9 +2,7 @@
from typing import assert_type
-from iterum import InfSeq
-from iterum import Seq
-from iterum import seq
+from iterum import InfSeq, Seq, seq
def seq_with_explicit_start_end_returns_seq():
diff --git a/type_tests/type_some.py b/type_tests/type_some.py
index e8efaad..861e515 100644
--- a/type_tests/type_some.py
+++ b/type_tests/type_some.py
@@ -1,25 +1,21 @@
from __future__ import annotations
-from typing import assert_type
-from typing import Literal
-from typing import TypeVar
-
-from .option_helpers import create_nil
-from .option_helpers import create_option
-from .option_helpers import create_some
-from .option_helpers import create_value
-from .option_helpers import map_to_value
-from .option_helpers import map_value_to_nil
-from .option_helpers import map_value_to_option
-from .option_helpers import map_value_to_some
-from .option_helpers import map_value_to_value
-from .option_helpers import predicate
-from iterum import iterum
-from iterum import Nil
-from iterum import nil
-from iterum import Option
-from iterum import Some
-from iterum import Swap
+from typing import Literal, TypeVar, assert_type
+
+from iterum import Nil, Option, Some, Swap, iterum, nil
+
+from .option_helpers import (
+ create_nil,
+ create_option,
+ create_some,
+ create_value,
+ map_to_value,
+ map_value_to_nil,
+ map_value_to_option,
+ map_value_to_some,
+ map_value_to_value,
+ predicate,
+)
T = TypeVar("T")
U = TypeVar("U")
@@ -126,8 +122,7 @@ def some_unwrap_or_else():
assert_type(some.unwrap_or_else(create_value), int)
-def create_other() -> str:
- ...
+def create_other() -> str: ...
def some_unzip():
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..8e20691
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,554 @@
+version = 1
+revision = 3
+requires-python = ">=3.14"
+
+[[package]]
+name = "click"
+version = "8.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "coverage"
+version = "7.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" },
+ { url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" },
+ { url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" },
+ { url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" },
+ { url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" },
+ { url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" },
+ { url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" },
+ { url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" },
+ { url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" },
+ { url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" },
+ { url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" },
+ { url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" },
+ { url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" },
+ { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" },
+]
+
+[[package]]
+name = "deepmerge"
+version = "2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" },
+]
+
+[[package]]
+name = "ghp-import"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
+]
+
+[[package]]
+name = "griffelib"
+version = "2.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "iterum"
+version = "0.2.0"
+source = { editable = "." }
+
+[package.dev-dependencies]
+dev = [
+ { name = "pyright" },
+ { name = "pytest" },
+ { name = "pytest-cov" },
+ { name = "ruff" },
+]
+docs = [
+ { name = "mkdocstrings-python" },
+ { name = "zensical" },
+]
+
+[package.metadata]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pyright", specifier = ">=1.1.409" },
+ { name = "pytest" },
+ { name = "pytest-cov" },
+ { name = "ruff", specifier = ">=0.8.0" },
+]
+docs = [
+ { name = "mkdocstrings-python" },
+ { name = "zensical" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+]
+
+[[package]]
+name = "mergedeep"
+version = "1.3.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" },
+]
+
+[[package]]
+name = "mkdocs"
+version = "1.6.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "ghp-import" },
+ { name = "jinja2" },
+ { name = "markdown" },
+ { name = "markupsafe" },
+ { name = "mergedeep" },
+ { name = "mkdocs-get-deps" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "pyyaml" },
+ { name = "pyyaml-env-tag" },
+ { name = "watchdog" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" },
+]
+
+[[package]]
+name = "mkdocs-autorefs"
+version = "1.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" },
+]
+
+[[package]]
+name = "mkdocs-get-deps"
+version = "0.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mergedeep" },
+ { name = "platformdirs" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" },
+]
+
+[[package]]
+name = "mkdocstrings"
+version = "1.0.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jinja2" },
+ { name = "markdown" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+ { name = "mkdocs-autorefs" },
+ { name = "pymdown-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1d/5d/f888d4d3eb31359b327bc9b17a212d6ef03fe0b0682fbb3fc2cb849fb12b/mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172", size = 100088, upload-time = "2026-04-15T09:16:53.376Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b", size = 35560, upload-time = "2026-04-15T09:16:51.436Z" },
+]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "2.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "griffelib" },
+ { name = "mkdocs-autorefs" },
+ { name = "mkdocstrings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" },
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.9.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+]
+
+[[package]]
+name = "pymdown-extensions"
+version = "10.21.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" },
+]
+
+[[package]]
+name = "pyright"
+version = "1.1.409"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nodeenv" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
+]
+
+[[package]]
+name = "pytest-cov"
+version = "7.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "coverage" },
+ { name = "pluggy" },
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "pyyaml-env-tag"
+version = "1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.15.13"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" },
+ { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" },
+ { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" },
+ { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" },
+ { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "watchdog"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
+ { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
+ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
+ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
+]
+
+[[package]]
+name = "zensical"
+version = "0.0.41"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "deepmerge" },
+ { name = "jinja2" },
+ { name = "markdown" },
+ { name = "pygments" },
+ { name = "pymdown-extensions" },
+ { name = "pyyaml" },
+ { name = "tomli" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/89/d6/b3e931233e53a2377ef5915cc6e786845c3263306874a469af8fb569ef9c/zensical-0.0.41.tar.gz", hash = "sha256:6c3c90301123749dfc26a210d6c080f0691253c7c765ad308a10b4518369a6fe", size = 3927788, upload-time = "2026-05-09T14:35:29.005Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/72/08/ee18207c9b4e3ada74a0f4adf253bea90da39ae43772761cd91072e3a1fc/zensical-0.0.41-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f06a0015dcfdf7aeca73f4998a401db65db0ae2dd72da9629a7be8f9a4d0b7b6", size = 12701539, upload-time = "2026-05-09T14:34:48.6Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/93/d4635fbbce8171cf71dd64285d9f6d5773a2b624b928f1dd8acaf1ee9f9f/zensical-0.0.41-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:4e524ce68c9ff082ffaded9f742407097cf51bab692b7bc18d3c174b966174fe", size = 12560038, upload-time = "2026-05-09T14:34:51.666Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/4a/1730a30377bbb0914ed740e0e289d379b0552673b6cf912aefe7a205440c/zensical-0.0.41-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4afe35331cd2394c408cd362458936479cc0ed4fb272478498e4794aafc7414", size = 12942926, upload-time = "2026-05-09T14:34:54.393Z" },
+ { url = "https://files.pythonhosted.org/packages/32/e3/d9a0416ef4edc043ce9f404a66f1934f102bcb645b103abb26b180ba5680/zensical-0.0.41-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a850285050f03aeb3b67ce7d99943093059fe8d32fc7731fa9f27be45c64cc", size = 12912711, upload-time = "2026-05-09T14:34:57.174Z" },
+ { url = "https://files.pythonhosted.org/packages/68/d0/775852783bef835425306a2fcd8236ef14fd19160e1b4261e192bf2d9f54/zensical-0.0.41-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35052e9dbefabe3a71c4836cfc4afa6c9469e5eeddc2a3ee750803ae3fe777dc", size = 13275869, upload-time = "2026-05-09T14:34:59.93Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/95/554273cc09a270ced0213d3e0aac8b3fc2b472fc2b26771d56fc8fd55047/zensical-0.0.41-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47f459205fb55f64dcb6c65e9f3c2fa00a2b4306c5ef1b71b9a50c45007071d", size = 12980177, upload-time = "2026-05-09T14:35:02.81Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/b5/d74d5040b3121db5c72b0134f0455641b90b1277fb1330a8e5e0029ca8d3/zensical-0.0.41-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:aa3b3b3a4e6f75f6bb3c1aca1fad7a96cebf54cbd4e31122f6876503b8801666", size = 13119629, upload-time = "2026-05-09T14:35:07.105Z" },
+ { url = "https://files.pythonhosted.org/packages/62/9a/93527acd7750092d7fca2e6c43fe2b8f1e85e1c96a1002baf6a08201c6f7/zensical-0.0.41-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:565133fd48b2ce939698c174c0c1c6470407a8fb6a90a2bb0eeec97cd4344444", size = 13182183, upload-time = "2026-05-09T14:35:10.105Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/7e/d77e4c809bfcbad40db85a6a7beeda2ee5c964232e0186783c3a837a7d0b/zensical-0.0.41-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:cec0a2b05eaaace0c7424bab3f2884da03ade212cac4ba4487c58691ec13ec65", size = 13330444, upload-time = "2026-05-09T14:35:13.245Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/e8/ecbb7e34bff88aa892c676b8b2e2ddf425f94d66cbb84b80016095191b77/zensical-0.0.41-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1736f0cb7686628cc6f53952d208423f20b542f0c16b0c2ddd7e702bf6e41fdd", size = 13263093, upload-time = "2026-05-09T14:35:20.826Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/6f/48b2f81ce708d19bb807d94716f2772ec4b74389b6d29024669fc470df08/zensical-0.0.41-cp310-abi3-win32.whl", hash = "sha256:34a78645c68fba152faacb66516c895283166154f8b15b61440a6c21c84f0974", size = 12253644, upload-time = "2026-05-09T14:35:23.598Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/92/5cf943133f61b996965743deeaff467f278135521f58d83ca68d2601ded3/zensical-0.0.41-cp310-abi3-win_amd64.whl", hash = "sha256:00d80cd573152e0efb655143bbdfe8788eb4b33167a802639fdb1b1800b724ac", size = 12483190, upload-time = "2026-05-09T14:35:26.43Z" },
+]
diff --git a/zensical.toml b/zensical.toml
new file mode 100644
index 0000000..806c89a
--- /dev/null
+++ b/zensical.toml
@@ -0,0 +1,42 @@
+[project]
+site_name = "iterum"
+site_description = "Rusty iterators in Python."
+site_author = "Tyler Smart"
+site_url = "https://tjsmart.github.io/iterum"
+
+repo_url = "https://github.com/tjsmart/iterum"
+repo_name = "tjsmart/iterum"
+
+copyright = "© 2023 Tyler Smart"
+
+extra_css = ["stylesheets/extra.css"]
+
+nav = [
+ { "Overview" = "index.md" },
+ { "API" = "api.md" },
+]
+
+[project.theme]
+logo = "assets/logo.png"
+favicon = "assets/logo.png"
+
+[project.theme.palette]
+scheme = "slate"
+primary = "red"
+accent = "red"
+
+[project.plugins.mkdocstrings.handlers.python]
+paths = ["src"]
+
+[project.plugins.mkdocstrings.handlers.python.options]
+docstring_style = "google"
+show_root_heading = true
+show_root_full_path = true
+show_source = true
+show_bases = true
+members_order = "source"
+show_if_no_docstring = true
+show_signature_annotations = true
+separate_signature = true
+show_symbol_type_heading = true
+show_symbol_type_toc = true