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 @@ [![PyPI - Version](https://img.shields.io/pypi/v/iterum.svg)](https://pypi.org/project/iterum) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/iterum.svg)](https://pypi.org/project/iterum) -[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) +[![project - uv](https://img.shields.io/badge/%F0%9F%A5%9A-uv-4051b5.svg)](https://github.com/astral-sh/uv) -[![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![types - Pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](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 @@ [![PyPI - Version](https://img.shields.io/pypi/v/iterum.svg)](https://pypi.org/project/iterum) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/iterum.svg)](https://pypi.org/project/iterum) -[![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) +[![project - uv](https://img.shields.io/badge/%F0%9F%A5%9A-uv-4051b5.svg)](https://github.com/astral-sh/uv) -[![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![types - Pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/) [![License - MIT](https://img.shields.io/badge/license-MIT-9400d3.svg)](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