From 776c35394412f62b822a992d4a6e1826cb5ef2c1 Mon Sep 17 00:00:00 2001 From: ed cuss Date: Thu, 24 Sep 2026 21:31:50 +0100 Subject: [PATCH 01/11] feat: result_v2 with tests --- README.md | 4 +- src/danom/_monads/__init__.py | 2 +- src/danom/_monads/_option.py | 25 +- src/danom/_monads/_result.py | 302 ------------------------ src/danom/_monads/_result_v2.py | 391 ++++++++++++++++++++++++++++++++ src/danom/_monads/_safe.py | 6 +- src/danom/_stream/_par.py | 2 +- src/danom/_stream/_sync.py | 2 +- tests/conftest.py | 6 +- tests/monads/test_monad_laws.py | 32 +-- tests/monads/test_result.py | 135 ----------- tests/monads/test_result_v2.py | 269 ++++++++++++++++++++++ tests/test_safe.py | 5 +- 13 files changed, 695 insertions(+), 486 deletions(-) delete mode 100644 src/danom/_monads/_result.py create mode 100644 src/danom/_monads/_result_v2.py delete mode 100644 tests/monads/test_result.py create mode 100644 tests/monads/test_result_v2.py diff --git a/README.md b/README.md index 6a36500..44ae1a4 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ Alternatively the map method can be used to return a new type instance with the │ │ ├── __init__.py │ │ ├── _either.py # A simple Either monad, includes the base Either, Right and Left. │ │ ├── _option.py -│ │ ├── _result.py # A simple Result monad, includes the base Result, Ok and Err. +│ │ ├── _result_v2.py │ │ └── _safe.py # decorators that except given exception types and return a monad of the result │ ├── _stream │ │ ├── __init__.py @@ -287,7 +287,7 @@ Alternatively the map method can be used to return a new type instance with the │ │ ├── test_either.py │ │ ├── test_monad_laws.py │ │ ├── test_option.py -│ │ └── test_result.py +│ │ └── test_result_v2.py │ ├── stream │ │ ├── __init__.py │ │ ├── _common.py diff --git a/src/danom/_monads/__init__.py b/src/danom/_monads/__init__.py index a01afd8..4fb9694 100644 --- a/src/danom/_monads/__init__.py +++ b/src/danom/_monads/__init__.py @@ -1,6 +1,6 @@ from ._either import Either, Left, Right from ._option import Null, Option, Some -from ._result import Err, Ok, Result +from ._result_v2 import Err, Ok, Result from ._safe import safe, safe_method __all__ = [ diff --git a/src/danom/_monads/_option.py b/src/danom/_monads/_option.py index 55297c0..46e4f73 100644 --- a/src/danom/_monads/_option.py +++ b/src/danom/_monads/_option.py @@ -3,11 +3,12 @@ from abc import ABC, abstractmethod from collections.abc import Callable from copy import deepcopy -from typing import cast +from typing import TYPE_CHECKING, cast import attrs -from ._result import Err, Ok, Result +if TYPE_CHECKING: + from ._result_v2 import Result @attrs.define(frozen=True) @@ -147,10 +148,14 @@ def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: return fn(self.inner) def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 - return Ok(self.inner) + from ._result_v2 import Ok, Result # noqa: PLC0415 + + return cast(Result[T, E], Ok(self.inner)) def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 - return Ok(self.inner) + from ._result_v2 import Ok, Result # noqa: PLC0415 + + return cast(Result[T, E], Ok(self.inner)) def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 return self @@ -162,8 +167,10 @@ def replace(self, value: T) -> Option[T]: return Some(value) def transpose(self) -> Result[Option[T], Option[T]]: + from ._result_v2 import Err, Ok, Result # noqa: PLC0415 + if isinstance(self.inner, Ok): - return Ok(Some(self.inner.inner)) + return cast(Result[Option[T], Option[T]], Ok(Some(self.inner.inner))) if isinstance(self.inner, Err): return cast(Result[Option[T], Option[T]], Err[Option[T]](Some(self.inner.error))) raise TypeError("inner must be a `Result` type") @@ -236,9 +243,13 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: return default() def ok_or[E](self, err: E) -> Result[T, E]: + from ._result_v2 import Err, Result # noqa: PLC0415 + return cast(Result[T, E], Err[E](err)) def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: + from ._result_v2 import Err, Result # noqa: PLC0415 + return cast(Result[T, E], Err[E](err())) def or_(self, opt_b: Option[T]) -> Option[T]: @@ -251,7 +262,9 @@ def replace(self, value: T) -> Option[T]: # noqa: ARG002 return self def transpose[E](self) -> Result[Option[T], E]: - return Ok(self) + from ._result_v2 import Ok, Result # noqa: PLC0415 + + return cast(Result[Option[T], E], Ok(self)) def unwrap(self) -> T: raise TypeError("Can't call `unwrap` on `Null`") diff --git a/src/danom/_monads/_result.py b/src/danom/_monads/_result.py deleted file mode 100644 index f13adf9..0000000 --- a/src/danom/_monads/_result.py +++ /dev/null @@ -1,302 +0,0 @@ -"""Result monad - -repo-map-desc: A simple Result monad, includes the base Result, Ok and Err. -""" - -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Callable -from types import TracebackType -from typing import Any, Concatenate, Literal, Never, ParamSpec, Self, TypeVar - -import attrs -from attrs.validators import instance_of - -T_co = TypeVar("T_co", covariant=True) -U_co = TypeVar("U_co", covariant=True) -E_co = TypeVar("E_co", bound=object, covariant=True) -F_co = TypeVar("F_co", bound=object, covariant=True) -P = ParamSpec("P") - -Mappable = Callable[Concatenate[T_co, P], U_co] -Bindable = Callable[Concatenate[T_co, P], "Result[T_co, E_co]"] -Recoverable = Callable[Concatenate[E_co, P], "Result[T_co, E_co]"] - - -@attrs.define(frozen=True) -class Result[T_co, E_co: object](ABC): - """``Result`` monad. Consists of ``Ok`` and ``Err`` for successful and failed operations respectively. - Each monad is a frozen instance to prevent further mutation. - """ - - @classmethod - def unit(cls, inner: T_co) -> Ok[T_co]: - """Unit method. Given an item of type ``T`` return ``Ok(T)`` - - .. doctest:: - - >>> from danom import Err, Ok, Result - - >>> Result.unit(0) == Ok(0) - True - - >>> Ok.unit(0) == Ok(0) - True - - >>> Err.unit(0) == Ok(0) - True - """ - return Ok(inner) - - @abstractmethod - def is_ok(self) -> bool: - """Returns ``True`` if the result type is ``Ok``. - Returns ``False`` if the result type is ``Err``. - - .. doctest:: - - >>> from danom import Err, Ok - - >>> Ok().is_ok() == True - True - - >>> Err().is_ok() == False - True - """ - ... - - @abstractmethod - def map[**P](self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Result[T_co, E_co]: - """Pipe a pure function and wrap the return value with ``Ok``. - Given an ``Err`` will return self. - - .. code-block:: python - - from danom import Err, Ok - - Ok(1).map(add_one) == Ok(2) - Err(error=TypeError()).map(add_one) == Err(error=TypeError()) - """ - ... - - @abstractmethod - def map_err[**P](self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Result[T_co, E_co]: - """Pipe a pure function and wrap the return value with ``Err``. - Given an ``Ok`` will return self. - - .. code-block:: python - - from danom import Err, Ok - - Err(error=TypeError()).map_err(type_err_to_value_err) == Err(error=ValueError()) - Ok(1).map(type_err_to_value_err) == Ok(1) - """ - ... - - @abstractmethod - def and_then[**P]( - self, func: Bindable, *args: P.args, **kwargs: P.kwargs - ) -> Result[T_co, E_co]: - """Pipe another function that returns a monad. For ``Err`` will return original error. - - .. code-block:: python - - from danom import Err, Ok - - Ok(1).and_then(add_one) == Ok(2) - Ok(1).and_then(raise_err) == Err(error=TypeError()) - Err(error=TypeError()).and_then(add_one) == Err(error=TypeError()) - Err(error=TypeError()).and_then(raise_value_err) == Err(error=TypeError()) - """ - ... - - @abstractmethod - def or_else[**P]( - self, func: Recoverable, *args: P.args, **kwargs: P.kwargs - ) -> Result[T_co, E_co]: - """Pipe a function that returns a monad to recover from an ``Err``. For ``Ok`` will return original ``Result``. - - .. code-block:: python - - from danom import Err, Ok - - Ok(1).or_else(replace_err_with_zero) == Ok(1) - Err(error=TypeError()).or_else(replace_err_with_zero) == Ok(0) - """ - ... - - @abstractmethod - def unwrap(self) -> T_co: - """Unwrap the ``Ok`` monad and get the inner value. - Unwrap the ``Err`` monad will raise the inner error. - - .. doctest:: - - >>> from danom import Err, Ok - - >>> Ok().unwrap() == None - True - - >>> Ok(1).unwrap() == 1 - True - - >>> Ok("ok").unwrap() == 'ok' - True - - >>> Err(error=TypeError()).unwrap() - Traceback (most recent call last): - ... - TypeError: - """ - ... - - @staticmethod - def result_is_ok(result: Result[T_co, E_co]) -> bool: - """Check whether the monad is ok. Allows for ``filter`` or ``partition`` in a ``Stream`` without needing a lambda or custom function. - - .. code-block:: python - - from danom import Stream, Result - - Stream.from_iterable([Ok(), Ok(), Err()]).filter(Result.result_is_ok).collect() == (Ok(), Ok()) - - """ - return result.is_ok() - - @staticmethod - def result_unwrap(result: Result[T_co, E_co]) -> T_co: - """Unwrap the ``Ok`` monad and get the inner value. - Unwrap the ``Err`` monad will raise the inner error. - - .. code-block:: python - - from danom import Err, Ok, Stream, Result - - oks, errs = Stream.from_iterable([Ok(1), Ok(2), Err()]).partition(Result.result_is_ok) - oks.map(Result.result_unwrap).collect == (1, 2) - - """ - return result.unwrap() - - def flatten(self) -> Result[T_co, E_co]: - """Flatten the monad. Will return the first ``Err`` or the lowest ``Ok`` instance. - - .. doctest:: - - >>> from danom import Err, Ok, Stream, Result - - >>> Ok(Ok(Ok(1))).flatten() == Ok(1) - True - - >>> Ok(Ok(Err())).flatten() == Err() - True - - """ - current = self - - while True: - if isinstance(current, Ok) and isinstance(current.inner, Result): - current = current.inner - elif isinstance(current, Err) and isinstance(current.error, Result): - current = current.error - else: - return current - - -@attrs.define(frozen=True, hash=True) -class Ok(Result[T_co, Never]): - inner: Any = attrs.field(default=None) - - def is_ok(self) -> Literal[True]: - return True - - def map[**P](self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Ok[T_co]: - return Ok(func(self.inner, *args, **kwargs)) - - def map_err[**P](self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 - return self - - def and_then[**P]( - self, func: Bindable, *args: P.args, **kwargs: P.kwargs - ) -> Result[T_co, E_co]: - return Ok(func(self.inner, *args, **kwargs)).flatten() - - def or_else[**P](self, func: Recoverable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 - return self - - def unwrap(self) -> T_co: - return self.inner - - -SafeArgs = tuple[tuple[Any, ...], dict[str, Any]] -SafeMethodArgs = tuple[object, tuple[Any, ...], dict[str, Any]] - - -@attrs.define(frozen=True) -class Err(Result[Never, E_co]): - error: Any = attrs.field(default=None) - input_args: tuple[()] | SafeArgs | SafeMethodArgs = attrs.field( - default=(), validator=instance_of(tuple), repr=False - ) - traceback: str = attrs.field(default="", validator=instance_of(str)) - details: list[dict[str, Any]] = attrs.field(factory=list, init=False, repr=False) - - def __attrs_post_init__(self) -> None: - if isinstance(self.error, Exception): - # little hack explained here: https://www.attrs.org/en/stable/init.html#post-init - object.__setattr__(self, "details", self._extract_details(self.error.__traceback__)) - - def _extract_details(self, tb: TracebackType | None) -> list[dict[str, Any]]: - trace_info = [] - while tb: - frame = tb.tb_frame - trace_info.append( - { - "file": frame.f_code.co_filename, - "func": frame.f_code.co_name, - "line_no": tb.tb_lineno, - "locals": frame.f_locals, - } - ) - tb = tb.tb_next - return trace_info - - def is_ok(self) -> Literal[False]: - return False - - def map[**P](self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 - return self - - def map_err[**P](self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Err[E_co]: - return Err( - func(self.error, *args, **kwargs), input_args=self.input_args, traceback=self.traceback - ) - - def and_then[**P](self, func: Bindable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 - return self - - def or_else[**P]( - self, func: Recoverable, *args: P.args, **kwargs: P.kwargs - ) -> Result[T_co, E_co]: # ty: ignore[invalid-method-override] - return Ok(func(self.error, *args, **kwargs)).flatten() - - def unwrap(self) -> T_co: - if isinstance(self.error, Exception): - raise self.error - raise ValueError(f"Err does not have a caught error to raise: {self.error = }") - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Err): - return False - - return all( - ( - type(self.error) is type(other.error), - str(self.error) == str(other.error), - self.input_args == other.input_args, - ) - ) - - def __hash__(self) -> int: - return hash(f"{type(self.error)}{self.error}{self.input_args}") diff --git a/src/danom/_monads/_result_v2.py b/src/danom/_monads/_result_v2.py new file mode 100644 index 0000000..4bfcd81 --- /dev/null +++ b/src/danom/_monads/_result_v2.py @@ -0,0 +1,391 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable +from copy import deepcopy +from typing import TYPE_CHECKING, Any, Concatenate, Never, cast + +import attrs +from attrs.validators import instance_of + +if TYPE_CHECKING: + from ._option import Option + + +@attrs.define(frozen=True) +class Result[T, E](ABC): + @classmethod + def unit(cls, inner: T) -> Result[T, E]: + """Unit method. Given an item of type ``T`` return ``Ok(T)`` + + .. doctest:: + + >>> from danom import Err, Ok, Result + + >>> Result.unit(0) == Ok(0) + True + + >>> Ok.unit(0) == Ok(0) + True + + >>> Err.unit(0) == Ok(0) + True + """ + return cast(Result[T, E], Ok(inner)) + + @staticmethod + def result_is_ok(result: Result[T, E]) -> bool: + """Check whether the monad is ok. Allows for ``filter`` or ``partition`` in a ``Stream`` without needing a lambda or custom function. + + .. code-block:: python + + from danom import Stream, Result + + Stream.from_iterable([Ok(), Ok(), Err()]).filter(Result.result_is_ok).collect() == (Ok(), Ok()) + + """ + return result.is_ok() + + @staticmethod + def result_unwrap(result: Result[T, E]) -> T: + """Unwrap the ``Ok`` monad and get the inner value. + Unwrap the ``Err`` monad will raise the inner error. + + .. code-block:: python + + from danom import Err, Ok, Stream, Result + + oks, errs = Stream.from_iterable([Ok(1), Ok(2), Err()]).partition(Result.result_is_ok) + oks.map(Result.result_unwrap).collect == (1, 2) + + """ + return result.unwrap() + + @abstractmethod + def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: ... + + @abstractmethod + def and_then[U, F, **P]( + self, fn: Callable[Concatenate[T, P], Result[U, F]], *args: P.args, **kwargs: P.kwargs + ) -> Result[U, F]: ... + + def cloned(self) -> Result[T, E]: + return deepcopy(self) + + @abstractmethod + def err(self) -> Option[E]: ... + + @abstractmethod + def expect(self, msg: str) -> T: ... + + @abstractmethod + def expect_err(self, msg: str) -> E: ... + + @abstractmethod + def flatten(self) -> Result[T, E]: ... + + @abstractmethod + def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: ... + + @abstractmethod + def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: ... + + @abstractmethod + def is_err(self) -> bool: ... + + @abstractmethod + def is_err_and(self, fn: Callable[[E], bool]) -> bool: ... + + @abstractmethod + def is_ok(self) -> bool: ... + + @abstractmethod + def is_ok_and(self, fn: Callable[[T], bool]) -> bool: ... + + @abstractmethod + def map[U, **P]( + self, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs + ) -> Result[U, E]: ... + + @abstractmethod + def map_err[F, **P]( + self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs + ) -> Result[T, F]: ... + + @abstractmethod + def map_or[U, **P]( + self, default: U, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs + ) -> U: ... + + @abstractmethod + def map_or_else[U, **P]( + self, + default: Callable[P, U], + fn: Callable[Concatenate[T, P], U], + *args: P.args, + **kwargs: P.kwargs, + ) -> U: ... + + @abstractmethod + def ok(self) -> Option[T]: ... + + @abstractmethod + def or_[F](self, res: Result[T, F]) -> Result[T, F]: ... + + @abstractmethod + def or_else[F, **P]( + self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs + ) -> Result[T, F]: ... + + @abstractmethod + def transpose(self) -> Option[Result[T, E]]: ... + + @abstractmethod + def unwrap(self) -> T: ... + + @abstractmethod + def unwrap_err(self) -> E: ... + + @abstractmethod + def unwrap_or(self, default: T) -> T: ... + + @abstractmethod + def unwrap_or_else(self, fn: Callable[[E], T]) -> T: ... + + +@attrs.define(frozen=True) +class Ok[T](Result[T, Never]): + inner: T = attrs.field(default=None) + + def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: + return res + + def and_then[U, E, **P]( + self, fn: Callable[Concatenate[T, P], Result[U, E]], *args: P.args, **kwargs: P.kwargs + ) -> Result[U, E]: + return fn(self.inner, *args, **kwargs) + + def err[E](self) -> Option[E]: + from ._option import Null # noqa: PLC0415 + + return Null() + + def expect(self, msg: str) -> T: # noqa: ARG002 + return self.inner + + def expect_err(self, msg: str) -> Never: + raise ValueError(msg) + + def flatten(self) -> Result[T, Never]: + if isinstance(self.inner, Result): + return cast(Result[T, Never], self.inner) + return cast(Result[T, Never], self) + + def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: + fn(deepcopy(self.inner)) + return self + + def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: ARG002 + return self + + def is_err(self) -> bool: + return False + + def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 + return False + + def is_ok(self) -> bool: + return True + + def is_ok_and(self, fn: Callable[[T], bool]) -> bool: + return fn(self.inner) + + def map[U, **P]( + self, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs + ) -> Result[U, Never]: + return Ok(fn(self.inner, *args, **kwargs)) + + def map_err[F, **P]( + self, + fn: Callable[Concatenate[Never, P], F], # noqa: ARG002 + *args: P.args, # noqa: ARG002 + **kwargs: P.kwargs, # noqa: ARG002 + ) -> Result[T, F]: + return cast(Result[T, F], self) + + def map_or[U, **P]( + self, + default: U, # noqa: ARG002 + fn: Callable[Concatenate[T, P], U], + *args: P.args, + **kwargs: P.kwargs, + ) -> U: + return fn(self.inner, *args, **kwargs) + + def map_or_else[U, **P]( + self, + default: Callable[P, U], # noqa: ARG002 + fn: Callable[Concatenate[T, P], U], + *args: P.args, + **kwargs: P.kwargs, + ) -> U: + return fn(self.inner, *args, **kwargs) + + def ok(self) -> Option[T]: + from ._option import Some # noqa: PLC0415 + + return Some(self.inner) + + def or_[F](self, res: Result[T, F]) -> Result[T, F]: # noqa: ARG002 + return cast(Result[T, F], self) + + def or_else[F, **P]( + self, + fn: Callable[Concatenate[Never, P], F], # noqa: ARG002 + *args: P.args, # noqa: ARG002 + **kwargs: P.kwargs, # noqa: ARG002 + ) -> Result[T, F]: + return cast(Result[T, F], self) + + def transpose(self) -> Option[Result[T, Never]]: + from ._option import Null, Some # noqa: PLC0415 + + if isinstance(self.inner, Some): + return Some(Ok(self.inner.inner)) + if isinstance(self.inner, Null): + return Null() + raise TypeError("inner must be an `Option` type") + + def unwrap(self) -> T: + return self.inner + + def unwrap_err(self) -> Never: + raise TypeError("Can't call `unwrap_err` on `Ok`") + + def unwrap_or(self, default: T) -> T: # noqa: ARG002 + return self.inner + + def unwrap_or_else(self, fn: Callable[[Never], T]) -> T: # noqa: ARG002 + return self.inner + + +SafeArgs = tuple[tuple[Any, ...], dict[str, Any]] +SafeMethodArgs = tuple[object, tuple[Any, ...], dict[str, Any]] + + +@attrs.define(frozen=True) +class Err[E](Result[Never, E]): + error: E = attrs.field(default=None) + input_args: tuple[()] | SafeArgs | SafeMethodArgs = attrs.field( + default=(), validator=instance_of(tuple), repr=False + ) + traceback: str = attrs.field(default="", validator=instance_of(str)) + + def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: # noqa: ARG002 + return cast(Result[U, F], self) + + def and_then[U, F, **P]( + self, + fn: Callable[Concatenate[Never, P], Result[U, F]], # noqa: ARG002 + *args: P.args, # noqa: ARG002 + **kwargs: P.kwargs, # noqa: ARG002 + ) -> Result[U, F]: + return cast(Result[U, F], self) + + def err(self) -> Option[E]: + from ._option import Some # noqa: PLC0415 + + return Some(self.error) + + def expect(self, msg: str) -> Never: + raise ValueError(msg) + + def expect_err(self, msg: str) -> E: # noqa: ARG002 + return self.error + + def flatten(self) -> Result[Never, E]: + return self + + def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG002 + return self + + def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: + fn(deepcopy(self.error)) + return self + + def is_err(self) -> bool: + return True + + def is_err_and(self, fn: Callable[[E], bool]) -> bool: + return fn(self.error) + + def is_ok(self) -> bool: + return False + + def is_ok_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 + return False + + def map[U, **P]( + self, + fn: Callable[Concatenate[Never, P], U], # noqa: ARG002 + *args: P.args, # noqa: ARG002 + **kwargs: P.kwargs, # noqa: ARG002 + ) -> Result[U, E]: + return cast(Result[U, E], self) + + def map_err[F, **P]( + self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs + ) -> Result[Never, F]: + return Err( + fn(self.error, *args, **kwargs), input_args=self.input_args, traceback=self.traceback + ) + + def map_or[U, **P]( + self, + default: U, + fn: Callable[Concatenate[Never, P], U], # noqa: ARG002 + *args: P.args, # noqa: ARG002 + **kwargs: P.kwargs, # noqa: ARG002 + ) -> U: + return default + + def map_or_else[U, **P]( + self, + default: Callable[P, U], + fn: Callable[Concatenate[Never, P], U], # noqa: ARG002 + *args: P.args, + **kwargs: P.kwargs, + ) -> U: + return default(*args, **kwargs) + + def ok(self) -> Option[Never]: + from ._option import Null # noqa: PLC0415 + + return Null() + + def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: + return res + + def or_else[F, **P]( + self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs + ) -> Result[Never, F]: + return cast(Result[Never, F], fn(self.error, *args, **kwargs)) + + def transpose(self) -> Option[Result[Never, E]]: + from ._option import Some # noqa: PLC0415 + + return Some(self) + + def unwrap(self) -> Never: + if isinstance(self.error, Exception): + raise self.error + raise TypeError("Can't call `unwrap` on `Err`") + + def unwrap_err(self) -> E: + return self.error + + def unwrap_or[U](self, default: U) -> U: + return default + + def unwrap_or_else[U](self, fn: Callable[[E], U]) -> U: + return fn(self.error) diff --git a/src/danom/_monads/_safe.py b/src/danom/_monads/_safe.py index bfcccd7..ace2b24 100644 --- a/src/danom/_monads/_safe.py +++ b/src/danom/_monads/_safe.py @@ -8,7 +8,7 @@ from collections.abc import Callable from typing import Concatenate, ParamSpec, TypeVar, overload -from ._result import Err, Ok, Result +from ._result_v2 import Err, Ok, Result T = TypeVar("T") P = ParamSpec("P") @@ -65,7 +65,7 @@ def decorator(func: Callable[P, U]) -> Callable[P, Result[U, Exception]]: @functools.wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> Result[U, Exception]: try: - return Ok(func(*args, **kwargs)) + return Ok(func(*args, **kwargs)) # ty: ignore[invalid-return-type] except errors as e: return Err(error=e, input_args=(args, kwargs), traceback=traceback.format_exc()) # ty: ignore[invalid-return-type] @@ -97,7 +97,7 @@ def add_one(self, a: int) -> int: @functools.wraps(func) def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> Result[U, Exception]: try: - return Ok(func(self, *args, **kwargs)) + return Ok(func(self, *args, **kwargs)) # ty: ignore[invalid-return-type] except Exception as e: # noqa: BLE001 return Err(error=e, input_args=(self, args, kwargs), traceback=traceback.format_exc()) # ty: ignore[invalid-return-type] diff --git a/src/danom/_stream/_par.py b/src/danom/_stream/_par.py index 4b51f01..2586a6a 100644 --- a/src/danom/_stream/_par.py +++ b/src/danom/_stream/_par.py @@ -56,7 +56,7 @@ def collect(self, *, workers: int = 4, use_threads: bool = False) -> tuple[U, .. def to_stream(self) -> Stream[T]: """Convert the ``ParStream`` to a synchronous ``Stream``.""" - from ._sync import Stream + from ._sync import Stream # noqa: PLC0415 return Stream(self.seq, self.ops) diff --git a/src/danom/_stream/_sync.py b/src/danom/_stream/_sync.py index 9956196..57fd7a1 100644 --- a/src/danom/_stream/_sync.py +++ b/src/danom/_stream/_sync.py @@ -131,7 +131,7 @@ def collect(self, *, workers: int = 4, use_threads: bool = False) -> tuple[U, .. def to_par(self) -> ParStream[T]: """Convert the ``Stream`` to a ``ParStream``.""" - from ._par import ParStream + from ._par import ParStream # noqa: PLC0415 return ParStream(self.seq, self.ops) diff --git a/tests/conftest.py b/tests/conftest.py index fab178d..ba0d125 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ from collections.abc import Awaitable, Callable from multiprocessing.managers import ListProxy from pathlib import Path -from typing import Any, NoReturn, Self +from typing import Any, NoReturn, Self, cast from danom import Err, Ok, Result, safe, safe_method @@ -98,9 +98,9 @@ def safe_add(a: int, b: int) -> int: def safe_add_one(x: float | str) -> Result[float | str, TypeError]: if isinstance(x, (int, float)): - return Ok(x + 1) + return cast(Result[float | str, TypeError], Ok(x + 1)) if isinstance(x, str): - return Ok(x + "1") + return cast(Result[float | str, TypeError], Ok(x + "1")) return Err(TypeError(f"unsupported type: {type(x)}")) diff --git a/tests/monads/test_monad_laws.py b/tests/monads/test_monad_laws.py index c694f61..f85f2cf 100644 --- a/tests/monads/test_monad_laws.py +++ b/tests/monads/test_monad_laws.py @@ -1,10 +1,9 @@ from __future__ import annotations -import pytest from hypothesis import given from hypothesis import strategies as st -from danom import Either, Err, Left, Ok, Result, Right, identity +from danom import Either, Err, Left, Ok, Result, Right def monad_tests( @@ -30,39 +29,16 @@ def test_monadic_associativity(monad, f, g, h) -> None: ).or_else(h) st_results = st.integers().map(ok_monad) | st.text().map(err_monad) - st_nested_results = st.recursive( - st_results, lambda children: st.one_of(children.map(ok_monad)), max_leaves=5 - ) - st_nested_errs = st.recursive( - st_results, lambda children: st.one_of(children.map(err_monad)), max_leaves=5 - ) - - @given(monad=st_nested_results) - def test_flatten_idempotent(monad) -> None: - assert monad.flatten().flatten() == monad.flatten() @given(monad=st_results) def test_flatten_noop_for_flat_monad(monad) -> None: assert monad.flatten() == monad - @given(monad=st_nested_results) - def test_and_then_flattens(monad) -> None: - assert monad.flatten() == monad.and_then(identity) - - @given(monad=st_nested_errs) - def test_or_else_flattens(monad) -> None: - if isinstance(monad, Result): - pytest.skip("or_else flatten law not defined for Result") - assert monad.flatten() == monad.or_else(identity) - return ( test_monadic_left_identity, test_monadic_right_identity, test_monadic_associativity, - test_flatten_idempotent, test_flatten_noop_for_flat_monad, - test_and_then_flattens, - test_or_else_flattens, ) @@ -70,10 +46,7 @@ def test_or_else_flattens(monad) -> None: test_result_left_identity, test_result_right_identity, test_result_associativity, - test_result_flatten_idempotent, test_result_flatten_noop_for_flat_monad, - test_result_and_then_flattens, - test_result_or_else_flattens, ) = monad_tests(Result, Ok, Err) @@ -81,8 +54,5 @@ def test_or_else_flattens(monad) -> None: test_either_left_identity, test_either_right_identity, test_either_associativity, - test_either_flatten_idempotent, test_either_flatten_noop_for_flat_monad, - test_either_and_then_flattens, - test_either_or_else_flattens, ) = monad_tests(Either, Right, Left) diff --git a/tests/monads/test_result.py b/tests/monads/test_result.py deleted file mode 100644 index 01a053a..0000000 --- a/tests/monads/test_result.py +++ /dev/null @@ -1,135 +0,0 @@ -from contextlib import nullcontext - -import pytest - -from danom import Err, Ok, Result -from tests.conftest import add_one - - -@pytest.mark.parametrize( - ("monad", "inner"), - [pytest.param(Ok, 0), pytest.param(Ok, "ok"), pytest.param(Err, 0), pytest.param(Result, 0)], -) -def test_unit(monad, inner): - assert monad.unit(inner) == Ok(inner) - - -@pytest.mark.parametrize( - ("left", "right", "expected_result"), - [ - pytest.param(Ok(), Err(), False), - pytest.param(Ok(), Ok(), True), - pytest.param(Err(), Err(), True), - ], -) -def test_result_equality(left, right, expected_result): - assert (left == right) == expected_result - - -@pytest.mark.parametrize( - ("monad", "expected_result", "expected_context"), - [pytest.param(Result, None, pytest.raises(TypeError))], -) -def test_result_unwrap(monad, expected_result, expected_context): - with expected_context: - assert monad().unwrap() == expected_result - - -@pytest.mark.parametrize( - ("monad", "expected_result", "expected_context"), - [ - pytest.param(Ok(None), None, nullcontext()), - pytest.param(Ok(0), 0, nullcontext()), - pytest.param(Ok("ok"), "ok", nullcontext()), - pytest.param(Err(error=TypeError("should raise this")), None, pytest.raises(TypeError)), - pytest.param(Err(error=ValueError("should raise this")), None, pytest.raises(ValueError)), - pytest.param(Err("some other err representation"), None, pytest.raises(ValueError)), - ], -) -def test_unwrap(monad, expected_result, expected_context): - with expected_context: - assert monad.unwrap() == expected_result - - -@pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Ok(None), True), pytest.param(Err(), False)] -) -def test_is_ok(monad, expected_result): - assert monad.is_ok() == expected_result - - -@pytest.mark.parametrize( - ("monad", "func", "expected_result"), - [pytest.param(Ok(0), add_one, Ok(1)), pytest.param(Err(), add_one, Err())], -) -def test_map(monad, func, expected_result): - assert monad.map(func) == expected_result - - -@pytest.mark.parametrize( - ("monad", "func", "expected_result"), - [pytest.param(Ok(0), add_one, Ok(0)), pytest.param(Err(0), add_one, Err(1))], -) -def test_map_err(monad, func, expected_result): - assert monad.map_err(func) == expected_result - - -class OnlyIsOk(Result): - def is_ok(self) -> bool: - return False - - -class OnlyUnwrap(Result): - def unwrap(self) -> None: - return None - - -@pytest.mark.parametrize("cls", [pytest.param(OnlyIsOk), pytest.param(OnlyUnwrap)]) -def test_raises_not_implemented(cls): - with pytest.raises(TypeError): - cls() - - -@pytest.mark.parametrize( - ("err", "expected_details"), - [pytest.param(TypeError("an invalid type"), []), pytest.param("A primative err", [])], -) -def test_err_details(err, expected_details): - monad = Err(error=err) - assert monad.details == expected_details - - -@pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Ok(None), True), pytest.param(Err(), False)] -) -def test_staticmethod_result_is_ok(monad, expected_result): - assert Result.result_is_ok(monad) == expected_result - - -@pytest.mark.parametrize( - ("monad", "expected_result", "expected_context"), - [ - pytest.param(Ok(None), None, nullcontext()), - pytest.param(Ok(0), 0, nullcontext()), - pytest.param(Ok("ok"), "ok", nullcontext()), - pytest.param(Err(error=TypeError("should raise this")), None, pytest.raises(TypeError)), - pytest.param(Err(error=ValueError("should raise this")), None, pytest.raises(ValueError)), - pytest.param(Err("some other err representation"), None, pytest.raises(ValueError)), - ], -) -def test_staticmethod_result_unwrap(monad, expected_result, expected_context): - with expected_context: - assert Result.result_unwrap(monad) == expected_result - - -@pytest.mark.parametrize( - ("monad", "expected_result"), - [ - pytest.param(Ok(Ok()), Ok()), - pytest.param(Ok(Err()), Err()), - pytest.param(Err(Ok()), Ok()), - pytest.param(Err(Err()), Err()), - ], -) -def test_flatten(monad, expected_result) -> None: - assert monad.flatten() == expected_result diff --git a/tests/monads/test_result_v2.py b/tests/monads/test_result_v2.py new file mode 100644 index 0000000..acef83e --- /dev/null +++ b/tests/monads/test_result_v2.py @@ -0,0 +1,269 @@ +from contextlib import nullcontext +from typing import cast + +import pytest + +from danom._monads._option import Null, Some +from danom._monads._result_v2 import Err, Ok, Result +from tests.conftest import add_one, is_even + + +@pytest.mark.parametrize( + ("monad", "res", "expected_result"), + [ + pytest.param(Ok(2), Err("late error"), Err("late error")), + pytest.param(Err("early error"), Ok("foo"), Err("early error")), + pytest.param(Err("not a 2"), Err("late error"), Err("not a 2")), + pytest.param(Ok(2), Ok("different result type"), Ok("different result type")), + ], +) +def test_and_(monad: Result, res, expected_result) -> None: + assert monad.and_(res) == expected_result + + +def must_be_less_than_10(x: int) -> Result[int, str]: + return cast(Result[int, str], Ok(x) if x < 10 else Err("too high")) + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [ + pytest.param(Ok(2), must_be_less_than_10, Ok(2)), + pytest.param(Ok(20), must_be_less_than_10, Err("too high")), + pytest.param(Err("not a number"), must_be_less_than_10, Err("not a number")), + ], +) +def test_and_then(monad: Result, fn, expected_result) -> None: + assert monad.and_then(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result"), [pytest.param(Ok(2), Ok(2)), pytest.param(Err(2), Err(2))] +) +def test_cloned(monad: Result, expected_result) -> None: + assert monad.cloned() == expected_result + assert id(monad) != id(expected_result) + + +@pytest.mark.parametrize( + ("monad", "expected_result"), + [pytest.param(Ok(2), Null()), pytest.param(Err("Nothing here"), Some("Nothing here"))], +) +def test_err(monad: Result, expected_result) -> None: + assert monad.err() == expected_result + + +@pytest.mark.parametrize( + ("monad", "msg", "expected_result", "expected_context"), + [ + pytest.param(Ok(2), "must be positive", 2, nullcontext()), + pytest.param(Err(), "must be positive", None, pytest.raises(ValueError)), + ], +) +def test_expect(monad: Result, msg, expected_result, expected_context) -> None: + with expected_context: + assert monad.expect(msg) == expected_result + + +@pytest.mark.parametrize( + ("monad", "msg", "expected_result", "expected_context"), + [ + pytest.param(Err(2), "must be err", 2, nullcontext()), + pytest.param(Ok(), "must be err", None, pytest.raises(ValueError)), + ], +) +def test_expect_err(monad: Result, msg, expected_result, expected_context) -> None: + with expected_context: + assert monad.expect_err(msg) == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result"), + [ + pytest.param(Ok(Ok(Ok(2))), Ok(Ok(2))), + pytest.param(Ok(Ok(2)), Ok(2)), + pytest.param(Ok(2), Ok(2)), + pytest.param(Err(), Err()), + ], +) +def test_flatten(monad: Result, expected_result) -> None: + assert monad.flatten() == expected_result + + +def append_to_list(x) -> None: + x.append(2) + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [ + pytest.param(Ok([1]), append_to_list, Ok([1])), + pytest.param(Err("un-appendable"), append_to_list, Err("un-appendable")), + ], +) +def test_inspect(monad: Result, fn, expected_result) -> None: + assert monad.inspect(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [ + pytest.param(Err([1]), append_to_list, Err([1])), + pytest.param(Ok("un-appendable"), append_to_list, Ok("un-appendable")), + ], +) +def test_inspect_err(monad: Result, fn, expected_result) -> None: + assert monad.inspect_err(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result"), [pytest.param(Ok(2), False), pytest.param(Err(2), True)] +) +def test_is_err(monad: Result, expected_result) -> None: + assert monad.is_err() == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [ + pytest.param(Err(1), is_even, False), + pytest.param(Err(2), is_even, True), + pytest.param(Ok(2), is_even, False), + ], +) +def test_is_err_and(monad: Result, fn, expected_result) -> None: + assert monad.is_err_and(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result"), [pytest.param(Ok(2), True), pytest.param(Err(2), False)] +) +def test_is_ok(monad: Result, expected_result) -> None: + assert monad.is_ok() == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [ + pytest.param(Ok(1), is_even, False), + pytest.param(Ok(2), is_even, True), + pytest.param(Err(2), is_even, False), + ], +) +def test_is_ok_and(monad: Result, fn, expected_result) -> None: + assert monad.is_ok_and(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [pytest.param(Ok(1), add_one, Ok(2)), pytest.param(Err(1), add_one, Err(1))], +) +def test_map(monad: Result, fn, expected_result) -> None: + assert monad.map(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [pytest.param(Err(1), add_one, Err(2)), pytest.param(Ok(1), add_one, Ok(1))], +) +def test_map_err(monad: Result, fn, expected_result) -> None: + assert monad.map_err(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "default", "fn", "expected_result"), + [pytest.param(Ok("foo"), 42, len, 3), pytest.param(Err(), 42, len, 42)], +) +def test_map_or(monad: Result, default, fn, expected_result) -> None: + assert monad.map_or(default, fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "default", "fn", "expected_result"), + [pytest.param(Ok("foo"), lambda: 42, len, 3), pytest.param(Err(), lambda: 42, len, 42)], +) +def test_map_or_else(monad: Result, default, fn, expected_result) -> None: + assert monad.map_or_else(default, fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result"), [pytest.param(Ok(2), Some(2)), pytest.param(Err(2), Null())] +) +def test_ok(monad: Result, expected_result) -> None: + assert monad.ok() == expected_result + + +@pytest.mark.parametrize( + ("monad", "res", "expected_result"), + [ + pytest.param(Ok(2), Err("foo"), Ok(2)), + pytest.param(Err("foo"), Ok(100), Ok(100)), + pytest.param(Ok(2), Ok(100), Ok(2)), + pytest.param(Err("foo"), Err("foo"), Err("foo")), + ], +) +def test_or_(monad: Result, res, expected_result) -> None: + assert monad.or_(res) == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [ + pytest.param(Ok("barbarians"), lambda _: Ok("vikings"), Ok("barbarians")), + pytest.param(Err("foo"), lambda _: Ok("vikings"), Ok("vikings")), + pytest.param(Err("foo"), Err, Err("foo")), + ], +) +def test_or_else(monad: Result, fn, expected_result) -> None: + assert monad.or_else(fn) == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result", "expected_context"), + [ + pytest.param(Ok(Some(5)), Some(Ok(5)), nullcontext()), + pytest.param(Ok(Null()), Null(), nullcontext()), + pytest.param(Err(), Some(Err()), nullcontext()), + pytest.param(Ok(2), None, pytest.raises(TypeError)), + ], +) +def test_transpose(monad: Result, expected_result, expected_context) -> None: + with expected_context: + assert monad.transpose() == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result", "expected_context"), + [pytest.param(Ok(2), 2, nullcontext()), pytest.param(Err(), None, pytest.raises(TypeError))], +) +def test_unwrap(monad: Result, expected_result, expected_context) -> None: + with expected_context: + assert monad.unwrap() == expected_result + + +@pytest.mark.parametrize( + ("monad", "expected_result", "expected_context"), + [ + pytest.param(Err("failed"), "failed", nullcontext()), + pytest.param(Ok(2), None, pytest.raises(TypeError)), + ], +) +def test_unwrap_err(monad: Result, expected_result, expected_context) -> None: + with expected_context: + assert monad.unwrap_err() == expected_result + + +@pytest.mark.parametrize( + ("monad", "default", "expected_result"), + [pytest.param(Ok("car"), "bike", "car"), pytest.param(Err(), "bike", "bike")], +) +def test_unwrap_or(monad: Result, default, expected_result) -> None: + assert monad.unwrap_or(default) == expected_result + + +@pytest.mark.parametrize( + ("monad", "fn", "expected_result"), + [pytest.param(Ok(4), lambda _: 20, 4), pytest.param(Err(), lambda _: 20, 20)], +) +def test_unwrap_or_else(monad: Result, fn, expected_result) -> None: + assert monad.unwrap_or_else(fn) == expected_result diff --git a/tests/test_safe.py b/tests/test_safe.py index 8923577..6a86a8a 100644 --- a/tests/test_safe.py +++ b/tests/test_safe.py @@ -76,7 +76,10 @@ def test_traceback(): if not isinstance(err, Err): raise TypeError("This should be an Err by now") - tb_lines = err.traceback.replace(str(REPO_ROOT), ".").splitlines() + tb_lines = [ + line.split("#")[0].rstrip() + for line in err.traceback.replace(str(REPO_ROOT), ".").splitlines() + ] missing_lines = [line for line in expected_lines if line not in tb_lines] From 2b924eb63e501f7e0fb8021ac4259838c39d741f Mon Sep 17 00:00:00 2001 From: ed cuss Date: Fri, 25 Sep 2026 20:12:59 +0100 Subject: [PATCH 02/11] feat: examples from tests --- README.md | 7 +++ dev_tools/create_examples/__init__.py | 0 .../create_examples/collection/__init__.py | 0 .../create_examples/collection/example.py | 41 ++++++++++++++ .../create_examples/collection/record.py | 56 +++++++++++++++++++ .../create_examples/collection/recorder.py | 41 ++++++++++++++ tests/conftest.py | 12 +++- tests/monads/test_option.py | 51 ++++++++--------- tests/monads/test_result_v2.py | 47 ++++++++-------- tests/test_safe.py | 1 - 10 files changed, 206 insertions(+), 50 deletions(-) create mode 100644 dev_tools/create_examples/__init__.py create mode 100644 dev_tools/create_examples/collection/__init__.py create mode 100644 dev_tools/create_examples/collection/example.py create mode 100644 dev_tools/create_examples/collection/record.py create mode 100644 dev_tools/create_examples/collection/recorder.py diff --git a/README.md b/README.md index 44ae1a4..7a1eb32 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,13 @@ Alternatively the map method can be used to return a new type instance with the │ ├── ci_tests.yaml │ └── publish.yaml ├── dev_tools +│ ├── create_examples +│ │ ├── collection +│ │ │ ├── __init__.py +│ │ │ ├── example.py +│ │ │ ├── record.py +│ │ │ └── recorder.py # User entrypoint `example` and it's inner class `Example` +│ │ └── __init__.py │ ├── __init__.py │ ├── update_cov.py │ └── update_readme.py diff --git a/dev_tools/create_examples/__init__.py b/dev_tools/create_examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dev_tools/create_examples/collection/__init__.py b/dev_tools/create_examples/collection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dev_tools/create_examples/collection/example.py b/dev_tools/create_examples/collection/example.py new file mode 100644 index 0000000..5daf5c6 --- /dev/null +++ b/dev_tools/create_examples/collection/example.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +import attrs + +from .record import ExampleRecord +from .recorder import _RECORDER, Recorder + + +@attrs.define(frozen=True) +class Example[T]: + fn: Callable + args: tuple[Any, ...] + kwargs: dict[str, Any] + actual_result: T + recorder: Recorder + + def __eq__(self, expected: T) -> bool: + self.recorder.record_example( + ExampleRecord.new(self.fn, self.args, self.kwargs, self.actual_result, expected) + ) + return self.actual_result == expected + + def __hash__(self) -> int: + hash_value = "".join( + [ + self.fn.__qualname__, + self.fn.__module__, + str(self.args), + str(self.kwargs), + str(self.actual_result), + ] + ) + return hash(hash_value) + + +def example(fn: Callable, *args: tuple[Any, ...], **kwargs: dict[str, Any]) -> Example: + value = fn(*args, **kwargs) + return Example(fn, args, kwargs, value, recorder=_RECORDER) diff --git a/dev_tools/create_examples/collection/record.py b/dev_tools/create_examples/collection/record.py new file mode 100644 index 0000000..41f2392 --- /dev/null +++ b/dev_tools/create_examples/collection/record.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import inspect +from collections.abc import Callable +from pathlib import Path +from typing import Any, Self + +import attrs + + +@attrs.define(frozen=True, eq=True) +class ExampleRecord: + cls_name: str + fn_name: str + module: str + src_file: str + ent_repr: str + args: tuple[Any, ...] + kwargs: dict[str, Any] + returned: Any + expected: Any + + @classmethod + def new( + cls, + fn: Callable, + args: tuple[Any, ...], + kwargs: dict[str, Any], + returned: Any, # noqa: ANN401 + expected: Any, # noqa: ANN401 + ) -> Self: + return cls( + cls_name=fn.__self__.__class__.__name__, + fn_name=fn.__name__, + module=fn.__module__, + src_file=str(Path(inspect.getsourcefile(fn))), + ent_repr=f"{fn.__self__!r}.{fn.__name__}" + if fn.__class__.__name__ == "method" + else fn.__name__, + args=tuple(_get_repr(arg) for arg in args), + kwargs={k: _get_repr(v) for k, v in kwargs.items()}, + returned=_get_repr(returned), + expected=_get_repr(expected), + ) + + def to_dict(self) -> dict[str, str]: + return attrs.asdict(self) + + +def _get_repr(arg: Any) -> str: # noqa: ANN401 + value = repr(arg) + return ( + value.removeprefix(" None: + self.path = Path(self.path) + + def record_example(self, example: ExampleRecord) -> Self: + self.records.append(example) + return self + + def prepare_files(self) -> Self: + self.files[self.path] = json.dumps([r.to_dict() for r in self.records], indent=2) + self.files[self.path.parent / ".gitignore"] = "# automatically created by papertrail\n*" + return self + + def write_examples(self) -> Self: + for path, data in self.files.items(): + path.write_text(data) + return self + + +_RECORDER = Recorder() diff --git a/tests/conftest.py b/tests/conftest.py index ba0d125..ad16957 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,16 +1,26 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Generator from multiprocessing.managers import ListProxy from pathlib import Path from typing import Any, NoReturn, Self, cast +import pytest + from danom import Err, Ok, Result, safe, safe_method +from dev_tools.create_examples.collection.recorder import _RECORDER REPO_ROOT = Path(__file__).parents[1] +@pytest.fixture(scope="session", autouse=True) +def collect_examples() -> Generator[Any, None, None]: + yield + _RECORDER.path = REPO_ROOT / ".papertrail_cache/examples.json" + _RECORDER.prepare_files().write_examples() + + def make_async(fn: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: async def wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 return fn(*args, **kwargs) diff --git a/tests/monads/test_option.py b/tests/monads/test_option.py index 8ae09f0..dc3d2e8 100644 --- a/tests/monads/test_option.py +++ b/tests/monads/test_option.py @@ -3,6 +3,7 @@ import pytest from danom import Err, Null, Ok, Option, Some +from dev_tools.create_examples.collection.example import example from tests.conftest import add_one, is_even @@ -16,7 +17,7 @@ ], ) def test_and_(monad: Option, opt_b, expected_result) -> None: - assert monad.and_(opt_b) == expected_result + assert example(monad.and_, opt_b) == expected_result def must_be_less_than_10(x: int) -> Option[int]: @@ -32,28 +33,28 @@ def must_be_less_than_10(x: int) -> Option[int]: ], ) def test_and_then(monad: Option, fn, expected_result) -> None: - assert monad.and_then(fn) == expected_result + assert example(monad.and_then, fn) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Some(2), [2]), pytest.param(Null(), [])] ) def test_as_list(monad: Option, expected_result) -> None: - assert monad.as_list() == expected_result + assert example(monad.as_list) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Some(2), (2,)), pytest.param(Null(), ())] ) def test_as_tuple(monad: Option, expected_result) -> None: - assert monad.as_tuple() == expected_result + assert example(monad.as_tuple) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Some(2), Some(2)), pytest.param(Null(), Null())] ) def test_cloned(monad: Option, expected_result) -> None: - assert monad.cloned() == expected_result + assert example(monad.cloned) == expected_result assert id(monad) != id(expected_result) @@ -66,7 +67,7 @@ def test_cloned(monad: Option, expected_result) -> None: ) def test_expect(monad: Option, msg, expected_result, expected_context) -> None: with expected_context: - assert monad.expect(msg) == expected_result + assert example(monad.expect, msg) == expected_result @pytest.mark.parametrize( @@ -78,7 +79,7 @@ def test_expect(monad: Option, msg, expected_result, expected_context) -> None: ], ) def test_filter_(monad: Option, predicate, expected_result) -> None: - assert monad.filter_(predicate) == expected_result + assert example(monad.filter_, predicate) == expected_result @pytest.mark.parametrize( @@ -91,7 +92,7 @@ def test_filter_(monad: Option, predicate, expected_result) -> None: ], ) def test_flatten(monad: Option, expected_result) -> None: - assert monad.flatten() == expected_result + assert example(monad.flatten) == expected_result def append_to_list(x) -> None: @@ -106,14 +107,14 @@ def append_to_list(x) -> None: ], ) def test_inspect(monad: Option, fn, expected_result) -> None: - assert monad.inspect(fn) == expected_result + assert example(monad.inspect, fn) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Some(2), False), pytest.param(Null(), True)] ) def test_is_none(monad: Option, expected_result) -> None: - assert monad.is_none() == expected_result + assert example(monad.is_none) == expected_result @pytest.mark.parametrize( @@ -125,14 +126,14 @@ def test_is_none(monad: Option, expected_result) -> None: ], ) def test_is_none_or(monad: Option, fn, expected_result) -> None: - assert monad.is_none_or(fn) == expected_result + assert example(monad.is_none_or, fn) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Some(2), True), pytest.param(Null(), False)] ) def test_is_some(monad: Option, expected_result) -> None: - assert monad.is_some() == expected_result + assert example(monad.is_some) == expected_result @pytest.mark.parametrize( @@ -144,7 +145,7 @@ def test_is_some(monad: Option, expected_result) -> None: ], ) def test_is_some_and(monad: Option, fn, expected_result) -> None: - assert monad.is_some_and(fn) == expected_result + assert example(monad.is_some_and, fn) == expected_result @pytest.mark.parametrize( @@ -152,7 +153,7 @@ def test_is_some_and(monad: Option, fn, expected_result) -> None: [pytest.param(Some(1), add_one, Some(2)), pytest.param(Null(), add_one, Null())], ) def test_map(monad: Option, fn, expected_result) -> None: - assert monad.map(fn) == expected_result + assert example(monad.map, fn) == expected_result @pytest.mark.parametrize( @@ -176,7 +177,7 @@ def test_map_or_else(monad: Option, default, fn, expected_result) -> None: [pytest.param(Some("foo"), 0, Ok("foo")), pytest.param(Null(), 0, Err(0))], ) def test_ok_or(monad: Option, err, expected_result) -> None: - assert monad.ok_or(err) == expected_result + assert example(monad.ok_or, err) == expected_result @pytest.mark.parametrize( @@ -184,7 +185,7 @@ def test_ok_or(monad: Option, err, expected_result) -> None: [pytest.param(Some("foo"), lambda: 0, Ok("foo")), pytest.param(Null(), lambda: 0, Err(0))], ) def test_ok_or_else(monad: Option, err, expected_result) -> None: - assert monad.ok_or_else(err) == expected_result + assert example(monad.ok_or_else, err) == expected_result @pytest.mark.parametrize( @@ -197,7 +198,7 @@ def test_ok_or_else(monad: Option, err, expected_result) -> None: ], ) def test_or_(monad: Option, opt_b, expected_result) -> None: - assert monad.or_(opt_b) == expected_result + assert example(monad.or_, opt_b) == expected_result @pytest.mark.parametrize( @@ -209,7 +210,7 @@ def test_or_(monad: Option, opt_b, expected_result) -> None: ], ) def test_or_else(monad: Option, opt_b, expected_result) -> None: - assert monad.or_else(opt_b) == expected_result + assert example(monad.or_else, opt_b) == expected_result @pytest.mark.parametrize( @@ -217,7 +218,7 @@ def test_or_else(monad: Option, opt_b, expected_result) -> None: [pytest.param(Some(2), 5, Some(5)), pytest.param(Null(), 3, Null())], ) def test_replace(monad: Option, value, expected_result) -> None: - assert monad.replace(value) == expected_result + assert example(monad.replace, value) == expected_result @pytest.mark.parametrize( @@ -231,7 +232,7 @@ def test_replace(monad: Option, value, expected_result) -> None: ) def test_transpose(monad: Option, expected_result, expected_context) -> None: with expected_context: - assert monad.transpose() == expected_result + assert example(monad.transpose) == expected_result @pytest.mark.parametrize( @@ -240,7 +241,7 @@ def test_transpose(monad: Option, expected_result, expected_context) -> None: ) def test_unwrap(monad: Option, expected_result, expected_context) -> None: with expected_context: - assert monad.unwrap() == expected_result + assert example(monad.unwrap) == expected_result @pytest.mark.parametrize( @@ -248,7 +249,7 @@ def test_unwrap(monad: Option, expected_result, expected_context) -> None: [pytest.param(Some("car"), "bike", "car"), pytest.param(Null(), "bike", "bike")], ) def test_unwrap_or(monad: Option, default, expected_result) -> None: - assert monad.unwrap_or(default) == expected_result + assert example(monad.unwrap_or, default) == expected_result @pytest.mark.parametrize( @@ -256,7 +257,7 @@ def test_unwrap_or(monad: Option, default, expected_result) -> None: [pytest.param(Some(4), lambda: 20, 4), pytest.param(Null(), lambda: 20, 20)], ) def test_unwrap_or_else(monad: Option, fn, expected_result) -> None: - assert monad.unwrap_or_else(fn) == expected_result + assert example(monad.unwrap_or_else, fn) == expected_result @pytest.mark.parametrize( @@ -268,7 +269,7 @@ def test_unwrap_or_else(monad: Option, fn, expected_result) -> None: ], ) def test_zip(monad: Option, other, expected_result) -> None: - assert monad.zip(other) == expected_result + assert example(monad.zip, other) == expected_result @pytest.mark.parametrize( @@ -280,4 +281,4 @@ def test_zip(monad: Option, other, expected_result) -> None: ], ) def test_unzip(monad: Option, expected_result) -> None: - assert monad.unzip() == expected_result + assert example(monad.unzip) == expected_result diff --git a/tests/monads/test_result_v2.py b/tests/monads/test_result_v2.py index acef83e..56f6587 100644 --- a/tests/monads/test_result_v2.py +++ b/tests/monads/test_result_v2.py @@ -5,6 +5,7 @@ from danom._monads._option import Null, Some from danom._monads._result_v2 import Err, Ok, Result +from dev_tools.create_examples.collection.example import example from tests.conftest import add_one, is_even @@ -18,7 +19,7 @@ ], ) def test_and_(monad: Result, res, expected_result) -> None: - assert monad.and_(res) == expected_result + assert example(monad.and_, res) == expected_result def must_be_less_than_10(x: int) -> Result[int, str]: @@ -34,14 +35,14 @@ def must_be_less_than_10(x: int) -> Result[int, str]: ], ) def test_and_then(monad: Result, fn, expected_result) -> None: - assert monad.and_then(fn) == expected_result + assert example(monad.and_then, fn) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), Ok(2)), pytest.param(Err(2), Err(2))] ) def test_cloned(monad: Result, expected_result) -> None: - assert monad.cloned() == expected_result + assert example(monad.cloned) == expected_result assert id(monad) != id(expected_result) @@ -50,7 +51,7 @@ def test_cloned(monad: Result, expected_result) -> None: [pytest.param(Ok(2), Null()), pytest.param(Err("Nothing here"), Some("Nothing here"))], ) def test_err(monad: Result, expected_result) -> None: - assert monad.err() == expected_result + assert example(monad.err) == expected_result @pytest.mark.parametrize( @@ -62,7 +63,7 @@ def test_err(monad: Result, expected_result) -> None: ) def test_expect(monad: Result, msg, expected_result, expected_context) -> None: with expected_context: - assert monad.expect(msg) == expected_result + assert example(monad.expect, msg) == expected_result @pytest.mark.parametrize( @@ -74,7 +75,7 @@ def test_expect(monad: Result, msg, expected_result, expected_context) -> None: ) def test_expect_err(monad: Result, msg, expected_result, expected_context) -> None: with expected_context: - assert monad.expect_err(msg) == expected_result + assert example(monad.expect_err, msg) == expected_result @pytest.mark.parametrize( @@ -87,7 +88,7 @@ def test_expect_err(monad: Result, msg, expected_result, expected_context) -> No ], ) def test_flatten(monad: Result, expected_result) -> None: - assert monad.flatten() == expected_result + assert example(monad.flatten) == expected_result def append_to_list(x) -> None: @@ -102,7 +103,7 @@ def append_to_list(x) -> None: ], ) def test_inspect(monad: Result, fn, expected_result) -> None: - assert monad.inspect(fn) == expected_result + assert example(monad.inspect, fn) == expected_result @pytest.mark.parametrize( @@ -113,14 +114,14 @@ def test_inspect(monad: Result, fn, expected_result) -> None: ], ) def test_inspect_err(monad: Result, fn, expected_result) -> None: - assert monad.inspect_err(fn) == expected_result + assert example(monad.inspect_err, fn) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), False), pytest.param(Err(2), True)] ) def test_is_err(monad: Result, expected_result) -> None: - assert monad.is_err() == expected_result + assert example(monad.is_err) == expected_result @pytest.mark.parametrize( @@ -132,14 +133,14 @@ def test_is_err(monad: Result, expected_result) -> None: ], ) def test_is_err_and(monad: Result, fn, expected_result) -> None: - assert monad.is_err_and(fn) == expected_result + assert example(monad.is_err_and, fn) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), True), pytest.param(Err(2), False)] ) def test_is_ok(monad: Result, expected_result) -> None: - assert monad.is_ok() == expected_result + assert example(monad.is_ok) == expected_result @pytest.mark.parametrize( @@ -151,7 +152,7 @@ def test_is_ok(monad: Result, expected_result) -> None: ], ) def test_is_ok_and(monad: Result, fn, expected_result) -> None: - assert monad.is_ok_and(fn) == expected_result + assert example(monad.is_ok_and, fn) == expected_result @pytest.mark.parametrize( @@ -159,7 +160,7 @@ def test_is_ok_and(monad: Result, fn, expected_result) -> None: [pytest.param(Ok(1), add_one, Ok(2)), pytest.param(Err(1), add_one, Err(1))], ) def test_map(monad: Result, fn, expected_result) -> None: - assert monad.map(fn) == expected_result + assert example(monad.map, fn) == expected_result @pytest.mark.parametrize( @@ -167,7 +168,7 @@ def test_map(monad: Result, fn, expected_result) -> None: [pytest.param(Err(1), add_one, Err(2)), pytest.param(Ok(1), add_one, Ok(1))], ) def test_map_err(monad: Result, fn, expected_result) -> None: - assert monad.map_err(fn) == expected_result + assert example(monad.map_err, fn) == expected_result @pytest.mark.parametrize( @@ -190,7 +191,7 @@ def test_map_or_else(monad: Result, default, fn, expected_result) -> None: ("monad", "expected_result"), [pytest.param(Ok(2), Some(2)), pytest.param(Err(2), Null())] ) def test_ok(monad: Result, expected_result) -> None: - assert monad.ok() == expected_result + assert example(monad.ok) == expected_result @pytest.mark.parametrize( @@ -203,7 +204,7 @@ def test_ok(monad: Result, expected_result) -> None: ], ) def test_or_(monad: Result, res, expected_result) -> None: - assert monad.or_(res) == expected_result + assert example(monad.or_, res) == expected_result @pytest.mark.parametrize( @@ -215,7 +216,7 @@ def test_or_(monad: Result, res, expected_result) -> None: ], ) def test_or_else(monad: Result, fn, expected_result) -> None: - assert monad.or_else(fn) == expected_result + assert example(monad.or_else, fn) == expected_result @pytest.mark.parametrize( @@ -229,7 +230,7 @@ def test_or_else(monad: Result, fn, expected_result) -> None: ) def test_transpose(monad: Result, expected_result, expected_context) -> None: with expected_context: - assert monad.transpose() == expected_result + assert example(monad.transpose) == expected_result @pytest.mark.parametrize( @@ -238,7 +239,7 @@ def test_transpose(monad: Result, expected_result, expected_context) -> None: ) def test_unwrap(monad: Result, expected_result, expected_context) -> None: with expected_context: - assert monad.unwrap() == expected_result + assert example(monad.unwrap) == expected_result @pytest.mark.parametrize( @@ -250,7 +251,7 @@ def test_unwrap(monad: Result, expected_result, expected_context) -> None: ) def test_unwrap_err(monad: Result, expected_result, expected_context) -> None: with expected_context: - assert monad.unwrap_err() == expected_result + assert example(monad.unwrap_err) == expected_result @pytest.mark.parametrize( @@ -258,7 +259,7 @@ def test_unwrap_err(monad: Result, expected_result, expected_context) -> None: [pytest.param(Ok("car"), "bike", "car"), pytest.param(Err(), "bike", "bike")], ) def test_unwrap_or(monad: Result, default, expected_result) -> None: - assert monad.unwrap_or(default) == expected_result + assert example(monad.unwrap_or, default) == expected_result @pytest.mark.parametrize( @@ -266,4 +267,4 @@ def test_unwrap_or(monad: Result, default, expected_result) -> None: [pytest.param(Ok(4), lambda _: 20, 4), pytest.param(Err(), lambda _: 20, 20)], ) def test_unwrap_or_else(monad: Result, fn, expected_result) -> None: - assert monad.unwrap_or_else(fn) == expected_result + assert example(monad.unwrap_or_else, fn) == expected_result diff --git a/tests/test_safe.py b/tests/test_safe.py index 6a86a8a..a379d7a 100644 --- a/tests/test_safe.py +++ b/tests/test_safe.py @@ -68,7 +68,6 @@ def test_traceback(): "Traceback (most recent call last):", ' File "./src/danom/_monads/_safe.py", line 68, in wrapper', " return Ok(func(*args, **kwargs))", - ' File "./tests/conftest.py", line 124, in div_zero', " return x / 0", "ZeroDivisionError: division by zero", ] From 6147d7872a9fcd68f92573fa3c0ba9c7d265a32a Mon Sep 17 00:00:00 2001 From: ed cuss Date: Fri, 25 Sep 2026 20:41:36 +0100 Subject: [PATCH 03/11] feat: create docs from examples --- README.md | 17 +++++++++++------ .../create_examples/collection/record.py | 15 +++++++++------ tests/conftest.py | 19 +++++++++++++++++-- tests/monads/test_option.py | 12 ++++++------ tests/monads/test_result_v2.py | 10 +++++----- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7a1eb32..7a6678c 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,12 @@ Alternatively the map method can be used to return a new type instance with the │ │ │ ├── __init__.py │ │ │ ├── example.py │ │ │ ├── record.py -│ │ │ └── recorder.py # User entrypoint `example` and it's inner class `Example` +│ │ │ └── recorder.py # User entrypoint `example` and it's inner class `Example` +│ │ ├── transformation +│ │ │ ├── __init__.py +│ │ │ ├── ast_editing.py +│ │ │ ├── format_examples.py +│ │ │ └── transform.py │ │ └── __init__.py │ ├── __init__.py │ ├── update_cov.py @@ -275,19 +280,19 @@ Alternatively the map method can be used to return a new type instance with the │ └── danom │ ├── _monads │ │ ├── __init__.py -│ │ ├── _either.py # A simple Either monad, includes the base Either, Right and Left. +│ │ ├── _either.py # A simple Either monad, includes the base Either, Right and Left. │ │ ├── _option.py │ │ ├── _result_v2.py -│ │ └── _safe.py # decorators that except given exception types and return a monad of the result +│ │ └── _safe.py # decorators that except given exception types and return a monad of the result │ ├── _stream │ │ ├── __init__.py │ │ ├── _async.py -│ │ ├── _base.py # the base class for Stream +│ │ ├── _base.py # the base class for Stream │ │ ├── _par.py │ │ └── _sync.py │ ├── __init__.py -│ ├── _new_type.py # function to create a new type, probably worth deprecating soon -│ └── _utils.py # random junk I can't think of place to put. compose, all_of, any_of, etc +│ ├── _new_type.py # function to create a new type, probably worth deprecating soon +│ └── _utils.py # random junk I can't think of place to put. compose, all_of, any_of, etc ├── tests │ ├── monads │ │ ├── __init__.py diff --git a/dev_tools/create_examples/collection/record.py b/dev_tools/create_examples/collection/record.py index 41f2392..7c7a231 100644 --- a/dev_tools/create_examples/collection/record.py +++ b/dev_tools/create_examples/collection/record.py @@ -48,9 +48,12 @@ def to_dict(self) -> dict[str, str]: def _get_repr(arg: Any) -> str: # noqa: ANN401 - value = repr(arg) - return ( - value.removeprefix(" str: + if raw.startswith("").split(".")[-1] + return raw diff --git a/tests/conftest.py b/tests/conftest.py index ad16957..afcb898 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,21 +4,24 @@ from collections.abc import Awaitable, Callable, Generator from multiprocessing.managers import ListProxy from pathlib import Path -from typing import Any, NoReturn, Self, cast +from typing import Any, Literal, NoReturn, Self, cast import pytest from danom import Err, Ok, Result, safe, safe_method +from danom._monads._option import Some from dev_tools.create_examples.collection.recorder import _RECORDER +from dev_tools.create_examples.transformation.transform import update_modified_docstrings REPO_ROOT = Path(__file__).parents[1] -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="session", autouse=False) def collect_examples() -> Generator[Any, None, None]: yield _RECORDER.path = REPO_ROOT / ".papertrail_cache/examples.json" _RECORDER.prepare_files().write_examples() + update_modified_docstrings(_RECORDER.path) def make_async(fn: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: @@ -134,6 +137,18 @@ def div_zero(x: int) -> float: return x / 0 +def get_some_vikings() -> Some[str]: + return Some("vikings") + + +def get_ok_vikings(*args) -> Ok[str]: # noqa: ARG001 ANN002 + return Ok("vikings") + + +def get_42(*args) -> Literal[42]: # noqa: ARG001 ANN002 + return 42 + + class Adder: def __init__(self) -> None: self.result = 0 diff --git a/tests/monads/test_option.py b/tests/monads/test_option.py index dc3d2e8..379743e 100644 --- a/tests/monads/test_option.py +++ b/tests/monads/test_option.py @@ -4,7 +4,7 @@ from danom import Err, Null, Ok, Option, Some from dev_tools.create_examples.collection.example import example -from tests.conftest import add_one, is_even +from tests.conftest import add_one, get_42, get_some_vikings, is_even @pytest.mark.parametrize( @@ -166,7 +166,7 @@ def test_map_or(monad: Option, default, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), - [pytest.param(Some("foo"), lambda: 42, len, 3), pytest.param(Null(), lambda: 42, len, 42)], + [pytest.param(Some("foo"), get_42, len, 3), pytest.param(Null(), get_42, len, 42)], ) def test_map_or_else(monad: Option, default, fn, expected_result) -> None: assert monad.map_or_else(default, fn) == expected_result @@ -182,7 +182,7 @@ def test_ok_or(monad: Option, err, expected_result) -> None: @pytest.mark.parametrize( ("monad", "err", "expected_result"), - [pytest.param(Some("foo"), lambda: 0, Ok("foo")), pytest.param(Null(), lambda: 0, Err(0))], + [pytest.param(Some("foo"), get_42, Ok("foo")), pytest.param(Null(), get_42, Err(42))], ) def test_ok_or_else(monad: Option, err, expected_result) -> None: assert example(monad.ok_or_else, err) == expected_result @@ -204,8 +204,8 @@ def test_or_(monad: Option, opt_b, expected_result) -> None: @pytest.mark.parametrize( ("monad", "opt_b", "expected_result"), [ - pytest.param(Some("barbarians"), lambda: Some("vikings"), Some("barbarians")), - pytest.param(Null(), lambda: Some("vikings"), Some("vikings")), + pytest.param(Some("barbarians"), get_some_vikings, Some("barbarians")), + pytest.param(Null(), get_some_vikings, Some("vikings")), pytest.param(Null(), Null, Null()), ], ) @@ -254,7 +254,7 @@ def test_unwrap_or(monad: Option, default, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Some(4), lambda: 20, 4), pytest.param(Null(), lambda: 20, 20)], + [pytest.param(Some(4), get_42, 4), pytest.param(Null(), get_42, 42)], ) def test_unwrap_or_else(monad: Option, fn, expected_result) -> None: assert example(monad.unwrap_or_else, fn) == expected_result diff --git a/tests/monads/test_result_v2.py b/tests/monads/test_result_v2.py index 56f6587..8398378 100644 --- a/tests/monads/test_result_v2.py +++ b/tests/monads/test_result_v2.py @@ -6,7 +6,7 @@ from danom._monads._option import Null, Some from danom._monads._result_v2 import Err, Ok, Result from dev_tools.create_examples.collection.example import example -from tests.conftest import add_one, is_even +from tests.conftest import add_one, get_42, get_ok_vikings, is_even @pytest.mark.parametrize( @@ -181,7 +181,7 @@ def test_map_or(monad: Result, default, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), - [pytest.param(Ok("foo"), lambda: 42, len, 3), pytest.param(Err(), lambda: 42, len, 42)], + [pytest.param(Ok("foo"), get_42, len, 3), pytest.param(Err(), get_42, len, 42)], ) def test_map_or_else(monad: Result, default, fn, expected_result) -> None: assert monad.map_or_else(default, fn) == expected_result @@ -210,8 +210,8 @@ def test_or_(monad: Result, res, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Ok("barbarians"), lambda _: Ok("vikings"), Ok("barbarians")), - pytest.param(Err("foo"), lambda _: Ok("vikings"), Ok("vikings")), + pytest.param(Ok("barbarians"), get_ok_vikings, Ok("barbarians")), + pytest.param(Err("foo"), get_ok_vikings, Ok("vikings")), pytest.param(Err("foo"), Err, Err("foo")), ], ) @@ -264,7 +264,7 @@ def test_unwrap_or(monad: Result, default, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Ok(4), lambda _: 20, 4), pytest.param(Err(), lambda _: 20, 20)], + [pytest.param(Ok(4), get_42, 4), pytest.param(Err(), get_42, 42)], ) def test_unwrap_or_else(monad: Result, fn, expected_result) -> None: assert example(monad.unwrap_or_else, fn) == expected_result From ec8828a485e5c126032f4983d40eced7912402ee Mon Sep 17 00:00:00 2001 From: ed cuss Date: Fri, 25 Sep 2026 21:10:58 +0100 Subject: [PATCH 04/11] docs: add examples --- coverage.svg | 2 +- .../transformation/__init__.py | 0 .../transformation/ast_editing.py | 99 ++ .../transformation/format_examples.py | 50 ++ .../transformation/transform.py | 16 + pyproject.toml | 1 + src/danom/_monads/_option.py | 843 +++++++++++++++++- src/danom/_monads/_result_v2.py | 768 +++++++++++++++- tests/conftest.py | 2 +- uv.lock | 85 ++ 10 files changed, 1818 insertions(+), 48 deletions(-) create mode 100644 dev_tools/create_examples/transformation/__init__.py create mode 100644 dev_tools/create_examples/transformation/ast_editing.py create mode 100644 dev_tools/create_examples/transformation/format_examples.py create mode 100644 dev_tools/create_examples/transformation/transform.py diff --git a/coverage.svg b/coverage.svg index ae5b368..cc997ea 100644 --- a/coverage.svg +++ b/coverage.svg @@ -13,4 +13,4 @@ coverage 100.00% - \ No newline at end of file + diff --git a/dev_tools/create_examples/transformation/__init__.py b/dev_tools/create_examples/transformation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dev_tools/create_examples/transformation/ast_editing.py b/dev_tools/create_examples/transformation/ast_editing.py new file mode 100644 index 0000000..4748d8f --- /dev/null +++ b/dev_tools/create_examples/transformation/ast_editing.py @@ -0,0 +1,99 @@ +import re +import textwrap + +import libcst as cst + + +def update_function_docstrings(code: str, examples: dict[str, str]) -> str: + """Add or update the example section in selected function docstrings.""" + module = cst.parse_module(code) + return module.visit(DocstringTransformer(examples)).code + + +class DocstringTransformer(cst.CSTTransformer): + """Add, append, or replace example sections in function docstrings.""" + + def __init__(self, examples: dict[str, str]) -> None: + self.examples = examples + + def leave_FunctionDef( # noqa: N802 + self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef + ) -> cst.FunctionDef: + example = self.examples.get(original_node.name.value, "") + if not example: + return updated_node + + body = updated_node.body + if isinstance(body, cst.IndentedBlock): + statements = body.body + docstring = _get_docstring(statements) + if docstring is None: + statements = (_make_docstring(example), *statements) + else: + index, statement, value = docstring + new_value = _update_docstring(value.raw_value, example) + replacement = statement.with_changes( + body=(cst.Expr(value=cst.SimpleString(new_value)),) + ) + statements = (*statements[:index], replacement, *statements[index + 1 :]) + + return updated_node.with_changes(body=body.with_changes(body=statements)) + + statements = tuple(cst.SimpleStatementLine(body=(statement,)) for statement in body.body) + return updated_node.with_changes( + body=cst.IndentedBlock(body=(_make_docstring(example), *statements), indent=" ") + ) + + +def _get_docstring( + statements: tuple[cst.BaseStatement, ...], +) -> tuple[int, cst.SimpleStatementLine, cst.SimpleString] | None: + if not statements or not isinstance(statements[0], cst.SimpleStatementLine): + return None + + statement = statements[0] + if len(statement.body) != 1 or not isinstance(statement.body[0], cst.Expr): + return None + + value = statement.body[0].value + if not isinstance(value, cst.SimpleString): + return None + + return 0, statement, value + + +def _make_docstring(example: str) -> cst.SimpleStatementLine: + value = _update_docstring("", example) + return cst.SimpleStatementLine(body=(cst.Expr(value=cst.SimpleString(value)),)) + + +def _update_docstring(docstring: str, example: str) -> str: + example = _format_example(example) + pattern = ( + r"(?ms)^[ \t]*Papertrail examples:\n.*?\n[ \t]*::" + r"|^[ \t]*\.\. code-block:: python\n.*?\n[ \t]*::" + ) + matches = list(re.finditer(pattern, docstring)) + if matches: + first = matches[0] + sections = [docstring[: first.start()], example] + end = first.end() + for match in matches[1:]: + sections.append(docstring[end : match.start()]) + end = match.end() + sections.append(docstring[end:]) + new_doc = "".join(sections) + else: + new_doc = f"{docstring}\n\n{example}" + + return f'"""{new_doc.strip()}\n """' + + +def _format_example(example: str) -> str: + lines = example.splitlines(keepends=True) + if not lines: + return "" + + return lines[0] + textwrap.indent( + "".join(lines[1:]), " ", predicate=lambda line: bool(line.strip()) + ) diff --git a/dev_tools/create_examples/transformation/format_examples.py b/dev_tools/create_examples/transformation/format_examples.py new file mode 100644 index 0000000..33f2243 --- /dev/null +++ b/dev_tools/create_examples/transformation/format_examples.py @@ -0,0 +1,50 @@ +from collections import defaultdict + +import black + +from ..collection.record import ExampleRecord # noqa: TID252 + + +def collect_example_strs(examples: list[dict]) -> dict[str, dict[str, list[str]]]: + fn_examples = defaultdict(_inner) + + for data in examples: + ex = ExampleRecord(**data) + + if ex.returned != ex.expected: + continue + + fn_examples[ex.src_file][ex.fn_name].append(example_to_str(ex)) + + return {k: dict(v) for k, v in fn_examples.items()} + + +def _inner() -> defaultdict: + return defaultdict(list) + + +def example_to_str(example: ExampleRecord) -> str: + sig = ", ".join( + part + for part in ( + ", ".join(map(str, example.args)), + ", ".join(f"{k}={v}" for k, v in example.kwargs.items()), + ) + if part + ) + expr = f"{example.ent_repr}({sig}) == {example.returned}" + formatted = black.format_str(expr, mode=black.Mode()) + lines = formatted.rstrip().splitlines() + doctest = "\n".join( + f"{' >>>' if i == 0 else ' ...'} {line}" for i, line in enumerate(lines) + ) + return f"{doctest}\n True" + + +def reduce_examples_to_example_str( + fn_examples: dict[str, dict[str, list[str]]], +) -> dict[str, dict[str, str]]: + return { + path: {k: ".. code-block:: python\n\n" + "\n\n".join(v) + "\n::" for k, v in fn.items()} + for path, fn in fn_examples.items() + } diff --git a/dev_tools/create_examples/transformation/transform.py b/dev_tools/create_examples/transformation/transform.py new file mode 100644 index 0000000..4850ef5 --- /dev/null +++ b/dev_tools/create_examples/transformation/transform.py @@ -0,0 +1,16 @@ +import json +from pathlib import Path + +from .ast_editing import update_function_docstrings +from .format_examples import collect_example_strs, reduce_examples_to_example_str + + +def update_modified_docstrings(examples_cache_path: Path) -> None: + examples = json.loads(examples_cache_path.read_text()) + reduced_examples = reduce_examples_to_example_str(collect_example_strs(examples)) + + for path, module_examples in reduced_examples.items(): + code = Path(path).read_text() + new_code = update_function_docstrings(code, module_examples) + if new_code != code: + Path(path).write_text(new_code) diff --git a/pyproject.toml b/pyproject.toml index 281bfd9..3505a3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ danom = ["py.typed"] dev = [ "hypothesis>=6.148.2", "ipykernel>=7.1.0", + "libcst>=1.9.0", "papertrail>=0.1.2", "pre-commit>=4.5.0", "pytest>=9.0.1", diff --git a/src/danom/_monads/_option.py b/src/danom/_monads/_option.py index 46e4f73..793f390 100644 --- a/src/danom/_monads/_option.py +++ b/src/danom/_monads/_option.py @@ -14,46 +14,206 @@ @attrs.define(frozen=True) class Option[T](ABC): @abstractmethod - def and_(self, opt_b: Option[T]) -> Option[T]: ... + def and_(self, opt_b: Option[T]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).and_(Null()) == Null() + True + + >>> Null().and_(Some(inner="foo")) == Null() + True + + >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") + True + + >>> Null().and_(Null()) == Null() + True + :: + """ + ... @abstractmethod - def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: ... + def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) + True + + >>> Some(inner=20).and_then(must_be_less_than_10) == Null() + True + + >>> Null().and_then(must_be_less_than_10) == Null() + True + :: + """ + ... @abstractmethod - def as_list(self) -> list[T]: ... + def as_list(self) -> list[T]: + """.. code-block:: python + + >>> Some(inner=2).as_list() == [2] + True + + >>> Null().as_list() == [] + True + :: + """ + ... @abstractmethod - def as_tuple(self) -> tuple[T, ...]: ... + def as_tuple(self) -> tuple[T, ...]: + """.. code-block:: python + + >>> Some(inner=2).as_tuple() == (2,) + True + + >>> Null().as_tuple() == () + True + :: + """ + ... def cloned(self) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).cloned() == Some(inner=2) + True + + >>> Null().cloned() == Null() + True + :: + """ return deepcopy(self) @abstractmethod - def expect(self, msg: str) -> T: ... + def expect(self, msg: str) -> T: + """.. code-block:: python + + >>> Some(inner=2).expect("must be positive") == 2 + True + :: + """ + ... @abstractmethod - def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: ... + def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=3).filter_(is_even) == Null() + True + + >>> Some(inner=4).filter_(is_even) == Some(inner=4) + True + + >>> Null().filter_(is_even) == Null() + True + :: + """ + ... @abstractmethod - def flatten(self) -> Option[T]: ... + def flatten(self) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) + True + + >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) + True + + >>> Some(inner=2).flatten() == Some(inner=2) + True + + >>> Null().flatten() == Null() + True + :: + """ + ... @abstractmethod - def inspect(self, fn: Callable[[T], None]) -> Option[T]: ... + def inspect(self, fn: Callable[[T], None]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) + True + + >>> Null().inspect(append_to_list) == Null() + True + :: + """ + ... @abstractmethod - def is_none(self) -> bool: ... + def is_none(self) -> bool: + """.. code-block:: python + + >>> Some(inner=2).is_none() == False + True + + >>> Null().is_none() == True + True + :: + """ + ... @abstractmethod - def is_none_or(self, fn: Callable[[T], bool]) -> bool: ... + def is_none_or(self, fn: Callable[[T], bool]) -> bool: + """.. code-block:: python + + >>> Some(inner=1).is_none_or(is_even) == False + True + + >>> Some(inner=2).is_none_or(is_even) == True + True + + >>> Null().is_none_or(is_even) == True + True + :: + """ + ... @abstractmethod - def is_some(self) -> bool: ... + def is_some(self) -> bool: + """.. code-block:: python + + >>> Some(inner=2).is_some() == True + True + + >>> Null().is_some() == False + True + :: + """ + ... @abstractmethod - def is_some_and(self, fn: Callable[[T], bool]) -> bool: ... + def is_some_and(self, fn: Callable[[T], bool]) -> bool: + """.. code-block:: python + + >>> Some(inner=1).is_some_and(is_even) == False + True + + >>> Some(inner=2).is_some_and(is_even) == True + True + + >>> Null().is_some_and(is_even) == False + True + :: + """ + ... @abstractmethod - def map[U](self, fn: Callable[[T], U]) -> Option[U]: ... + def map[U](self, fn: Callable[[T], U]) -> Option[U]: + """.. code-block:: python + + >>> Some(inner=1).map(add_one) == Some(inner=2) + True + + >>> Null().map(add_one) == Null() + True + :: + """ + ... @abstractmethod def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: ... @@ -62,37 +222,164 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: ... def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: ... @abstractmethod - def ok_or[E](self, err: E) -> Result[T, E]: ... + def ok_or[E](self, err: E) -> Result[T, E]: + """.. code-block:: python + + >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") + True + + >>> Null().ok_or(0) == Err(error=0, traceback="") + True + :: + """ + ... @abstractmethod - def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: ... + def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: + """.. code-block:: python + + >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") + True + + >>> Null().ok_or_else(get_42) == Err(error=42, traceback="") + True + :: + """ + ... @abstractmethod - def or_(self, opt_b: Option[T]) -> Option[T]: ... + def or_(self, opt_b: Option[T]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).or_(Null()) == Some(inner=2) + True + + >>> Null().or_(Some(inner=100)) == Some(inner=100) + True + + >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) + True + + >>> Null().or_(Null()) == Null() + True + :: + """ + ... @abstractmethod - def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: ... + def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") + True + + >>> Null().or_else(get_some_vikings) == Some(inner="vikings") + True + + >>> Null().or_else(Null) == Null() + True + :: + """ + ... @abstractmethod - def replace(self, value: T) -> Option[T]: ... + def replace(self, value: T) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).replace(5) == Some(inner=5) + True + + >>> Null().replace(3) == Null() + True + :: + """ + ... @abstractmethod - def transpose[E](self) -> Result[Option[T], E]: ... + def transpose[E](self) -> Result[Option[T], E]: + """.. code-block:: python + + >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) + True + + >>> Some(inner=Err(error=2, traceback="")).transpose() == Err( + ... error=Some(inner=2), traceback="" + ... ) + True + + >>> Null().transpose() == Ok(inner=Null()) + True + :: + """ + ... @abstractmethod - def unwrap(self) -> T: ... + def unwrap(self) -> T: + """.. code-block:: python + + >>> Some(inner=2).unwrap() == 2 + True + :: + """ + ... @abstractmethod - def unwrap_or(self, default: T) -> T: ... + def unwrap_or(self, default: T) -> T: + """.. code-block:: python + + >>> Some(inner="car").unwrap_or("bike") == "car" + True + + >>> Null().unwrap_or("bike") == "bike" + True + :: + """ + ... @abstractmethod - def unwrap_or_else(self, fn: Callable[..., T]) -> T: ... + def unwrap_or_else(self, fn: Callable[..., T]) -> T: + """.. code-block:: python + + >>> Some(inner=4).unwrap_or_else(get_42) == 4 + True + + >>> Null().unwrap_or_else(get_42) == 42 + True + :: + """ + ... @abstractmethod - def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: ... + def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: + """.. code-block:: python + + >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) + True + + >>> Some(inner=1).zip(Null()) == Null() + True + + >>> Null().zip(Some(inner=1)) == Null() + True + :: + """ + ... @abstractmethod - def unzip[U](self) -> tuple[Option[T], Option[U]]: ... + def unzip[U](self) -> tuple[Option[T], Option[U]]: + """.. code-block:: python + + >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) + True + + >>> Some(inner=4).unzip() == (Null(), Null()) + True + + >>> Null().unzip() == (Null(), Null()) + True + :: + """ + ... @attrs.define(frozen=True) @@ -100,45 +387,183 @@ class Some[T](Option): inner: T def and_(self, opt_b: Option[T]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).and_(Null()) == Null() + True + + >>> Null().and_(Some(inner="foo")) == Null() + True + + >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") + True + + >>> Null().and_(Null()) == Null() + True + :: + """ return opt_b def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: + """.. code-block:: python + + >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) + True + + >>> Some(inner=20).and_then(must_be_less_than_10) == Null() + True + + >>> Null().and_then(must_be_less_than_10) == Null() + True + :: + """ return fn(self.inner) def as_list(self) -> list[T]: + """.. code-block:: python + + >>> Some(inner=2).as_list() == [2] + True + + >>> Null().as_list() == [] + True + :: + """ return [self.inner] def as_tuple(self) -> tuple[T, ...]: + """.. code-block:: python + + >>> Some(inner=2).as_tuple() == (2,) + True + + >>> Null().as_tuple() == () + True + :: + """ return (self.inner,) def expect(self, msg: str) -> T: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=2).expect("must be positive") == 2 + True + :: + """ return self.inner def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=3).filter_(is_even) == Null() + True + + >>> Some(inner=4).filter_(is_even) == Some(inner=4) + True + + >>> Null().filter_(is_even) == Null() + True + :: + """ return self if predicate(self.inner) else Null() def flatten(self) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) + True + + >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) + True + + >>> Some(inner=2).flatten() == Some(inner=2) + True + + >>> Null().flatten() == Null() + True + :: + """ if isinstance(self.inner, Some): return self.inner return self def inspect(self, fn: Callable[[T], None]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) + True + + >>> Null().inspect(append_to_list) == Null() + True + :: + """ fn(deepcopy(self.inner)) return self def is_none(self) -> bool: + """.. code-block:: python + + >>> Some(inner=2).is_none() == False + True + + >>> Null().is_none() == True + True + :: + """ return False def is_none_or(self, fn: Callable[[T], bool]) -> bool: + """.. code-block:: python + + >>> Some(inner=1).is_none_or(is_even) == False + True + + >>> Some(inner=2).is_none_or(is_even) == True + True + + >>> Null().is_none_or(is_even) == True + True + :: + """ return fn(self.inner) def is_some(self) -> bool: + """.. code-block:: python + + >>> Some(inner=2).is_some() == True + True + + >>> Null().is_some() == False + True + :: + """ return True def is_some_and(self, fn: Callable[[T], bool]) -> bool: + """.. code-block:: python + + >>> Some(inner=1).is_some_and(is_even) == False + True + + >>> Some(inner=2).is_some_and(is_even) == True + True + + >>> Null().is_some_and(is_even) == False + True + :: + """ return fn(self.inner) def map[U](self, fn: Callable[[T], U]) -> Option[U]: + """.. code-block:: python + + >>> Some(inner=1).map(add_one) == Some(inner=2) + True + + >>> Null().map(add_one) == Null() + True + :: + """ return Some(fn(self.inner)) def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 @@ -148,25 +573,93 @@ def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: return fn(self.inner) def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") + True + + >>> Null().ok_or(0) == Err(error=0, traceback="") + True + :: + """ from ._result_v2 import Ok, Result # noqa: PLC0415 return cast(Result[T, E], Ok(self.inner)) def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") + True + + >>> Null().ok_or_else(get_42) == Err(error=42, traceback="") + True + :: + """ from ._result_v2 import Ok, Result # noqa: PLC0415 return cast(Result[T, E], Ok(self.inner)) def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=2).or_(Null()) == Some(inner=2) + True + + >>> Null().or_(Some(inner=100)) == Some(inner=100) + True + + >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) + True + + >>> Null().or_(Null()) == Null() + True + :: + """ return self def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") + True + + >>> Null().or_else(get_some_vikings) == Some(inner="vikings") + True + + >>> Null().or_else(Null) == Null() + True + :: + """ return self def replace(self, value: T) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).replace(5) == Some(inner=5) + True + + >>> Null().replace(3) == Null() + True + :: + """ return Some(value) def transpose(self) -> Result[Option[T], Option[T]]: + """.. code-block:: python + + >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) + True + + >>> Some(inner=Err(error=2, traceback="")).transpose() == Err( + ... error=Some(inner=2), traceback="" + ... ) + True + + >>> Null().transpose() == Ok(inner=Null()) + True + :: + """ from ._result_v2 import Err, Ok, Result # noqa: PLC0415 if isinstance(self.inner, Ok): @@ -176,20 +669,68 @@ def transpose(self) -> Result[Option[T], Option[T]]: raise TypeError("inner must be a `Result` type") def unwrap(self) -> T: + """.. code-block:: python + + >>> Some(inner=2).unwrap() == 2 + True + :: + """ return self.inner def unwrap_or(self, default: T) -> T: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="car").unwrap_or("bike") == "car" + True + + >>> Null().unwrap_or("bike") == "bike" + True + :: + """ return self.inner def unwrap_or_else(self, fn: Callable[[], T]) -> T: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=4).unwrap_or_else(get_42) == 4 + True + + >>> Null().unwrap_or_else(get_42) == 42 + True + :: + """ return self.inner def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: + """.. code-block:: python + + >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) + True + + >>> Some(inner=1).zip(Null()) == Null() + True + + >>> Null().zip(Some(inner=1)) == Null() + True + :: + """ if isinstance(other, Some): return Some[tuple[T, U]]((self.inner, other.inner)) return Null[tuple[T, U]]() def unzip[U](self) -> tuple[Option[T], Option[U]]: + """.. code-block:: python + + >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) + True + + >>> Some(inner=4).unzip() == (Null(), Null()) + True + + >>> Null().unzip() == (Null(), Null()) + True + :: + """ if isinstance(self.inner, tuple) and len(self.inner) == 2: # noqa: PLR2004 return (Some(self.inner[0]), Some(self.inner[1])) return (Null(), Null()) @@ -198,42 +739,180 @@ def unzip[U](self) -> tuple[Option[T], Option[U]]: @attrs.define(frozen=True) class Null[T](Option): def and_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=2).and_(Null()) == Null() + True + + >>> Null().and_(Some(inner="foo")) == Null() + True + + >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") + True + + >>> Null().and_(Null()) == Null() + True + :: + """ return self def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) + True + + >>> Some(inner=20).and_then(must_be_less_than_10) == Null() + True + + >>> Null().and_then(must_be_less_than_10) == Null() + True + :: + """ return self def as_list(self) -> list[T]: + """.. code-block:: python + + >>> Some(inner=2).as_list() == [2] + True + + >>> Null().as_list() == [] + True + :: + """ return [] def as_tuple(self) -> tuple[T, ...]: + """.. code-block:: python + + >>> Some(inner=2).as_tuple() == (2,) + True + + >>> Null().as_tuple() == () + True + :: + """ return () def expect(self, msg: str) -> T: + """.. code-block:: python + + >>> Some(inner=2).expect("must be positive") == 2 + True + :: + """ raise ValueError(msg) def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=3).filter_(is_even) == Null() + True + + >>> Some(inner=4).filter_(is_even) == Some(inner=4) + True + + >>> Null().filter_(is_even) == Null() + True + :: + """ return self def flatten(self) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) + True + + >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) + True + + >>> Some(inner=2).flatten() == Some(inner=2) + True + + >>> Null().flatten() == Null() + True + :: + """ return self def inspect(self, fn: Callable[[T], None]) -> Option[T]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) + True + + >>> Null().inspect(append_to_list) == Null() + True + :: + """ return self def is_none(self) -> bool: + """.. code-block:: python + + >>> Some(inner=2).is_none() == False + True + + >>> Null().is_none() == True + True + :: + """ return True def is_none_or(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=1).is_none_or(is_even) == False + True + + >>> Some(inner=2).is_none_or(is_even) == True + True + + >>> Null().is_none_or(is_even) == True + True + :: + """ return True def is_some(self) -> bool: + """.. code-block:: python + + >>> Some(inner=2).is_some() == True + True + + >>> Null().is_some() == False + True + :: + """ return False def is_some_and(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=1).is_some_and(is_even) == False + True + + >>> Some(inner=2).is_some_and(is_even) == True + True + + >>> Null().is_some_and(is_even) == False + True + :: + """ return False def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=1).map(add_one) == Some(inner=2) + True + + >>> Null().map(add_one) == Null() + True + :: + """ return self def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 @@ -243,40 +922,156 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: return default() def ok_or[E](self, err: E) -> Result[T, E]: + """.. code-block:: python + + >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") + True + + >>> Null().ok_or(0) == Err(error=0, traceback="") + True + :: + """ from ._result_v2 import Err, Result # noqa: PLC0415 return cast(Result[T, E], Err[E](err)) def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: + """.. code-block:: python + + >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") + True + + >>> Null().ok_or_else(get_42) == Err(error=42, traceback="") + True + :: + """ from ._result_v2 import Err, Result # noqa: PLC0415 return cast(Result[T, E], Err[E](err())) def or_(self, opt_b: Option[T]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner=2).or_(Null()) == Some(inner=2) + True + + >>> Null().or_(Some(inner=100)) == Some(inner=100) + True + + >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) + True + + >>> Null().or_(Null()) == Null() + True + :: + """ return opt_b def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: + """.. code-block:: python + + >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") + True + + >>> Null().or_else(get_some_vikings) == Some(inner="vikings") + True + + >>> Null().or_else(Null) == Null() + True + :: + """ return opt_b() def replace(self, value: T) -> Option[T]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=2).replace(5) == Some(inner=5) + True + + >>> Null().replace(3) == Null() + True + :: + """ return self def transpose[E](self) -> Result[Option[T], E]: + """.. code-block:: python + + >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) + True + + >>> Some(inner=Err(error=2, traceback="")).transpose() == Err( + ... error=Some(inner=2), traceback="" + ... ) + True + + >>> Null().transpose() == Ok(inner=Null()) + True + :: + """ from ._result_v2 import Ok, Result # noqa: PLC0415 return cast(Result[Option[T], E], Ok(self)) def unwrap(self) -> T: + """.. code-block:: python + + >>> Some(inner=2).unwrap() == 2 + True + :: + """ raise TypeError("Can't call `unwrap` on `Null`") def unwrap_or(self, default: T) -> T: + """.. code-block:: python + + >>> Some(inner="car").unwrap_or("bike") == "car" + True + + >>> Null().unwrap_or("bike") == "bike" + True + :: + """ return default def unwrap_or_else(self, fn: Callable[[], T]) -> T: + """.. code-block:: python + + >>> Some(inner=4).unwrap_or_else(get_42) == 4 + True + + >>> Null().unwrap_or_else(get_42) == 42 + True + :: + """ return fn() def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) + True + + >>> Some(inner=1).zip(Null()) == Null() + True + + >>> Null().zip(Some(inner=1)) == Null() + True + :: + """ return self def unzip[U](self) -> tuple[Option[T], Option[U]]: + """.. code-block:: python + + >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) + True + + >>> Some(inner=4).unzip() == (Null(), Null()) + True + + >>> Null().unzip() == (Null(), Null()) + True + :: + """ return (Null(), Null()) diff --git a/src/danom/_monads/_result_v2.py b/src/danom/_monads/_result_v2.py index 4bfcd81..21c59cb 100644 --- a/src/danom/_monads/_result_v2.py +++ b/src/danom/_monads/_result_v2.py @@ -62,55 +62,229 @@ def result_unwrap(result: Result[T, E]) -> T: return result.unwrap() @abstractmethod - def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: ... + def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: + """.. code-block:: python + + >>> Ok(inner=2).and_(Err(error="late error", traceback="")) == Err( + ... error="late error", traceback="" + ... ) + True + + >>> Err(error="early error", traceback="").and_(Ok(inner="foo")) == Err( + ... error="early error", traceback="" + ... ) + True + + >>> Err(error="not a 2", traceback="").and_(Err(error="late error", traceback="")) == Err( + ... error="not a 2", traceback="" + ... ) + True + + >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") + True + :: + """ + ... @abstractmethod def and_then[U, F, **P]( self, fn: Callable[Concatenate[T, P], Result[U, F]], *args: P.args, **kwargs: P.kwargs - ) -> Result[U, F]: ... + ) -> Result[U, F]: + """.. code-block:: python + + >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) + True + + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high", traceback="") + True + + >>> Err(error="not a number", traceback="").and_then(must_be_less_than_10) == Err( + ... error="not a number", traceback="" + ... ) + True + :: + """ + ... def cloned(self) -> Result[T, E]: + """.. code-block:: python + + >>> Ok(inner=2).cloned() == Ok(inner=2) + True + + >>> Err(error=2, traceback="").cloned() == Err(error=2, traceback="") + True + :: + """ return deepcopy(self) @abstractmethod - def err(self) -> Option[E]: ... + def err(self) -> Option[E]: + """.. code-block:: python + + >>> Ok(inner=2).err() == Null() + True + + >>> Err(error="Nothing here", traceback="").err() == Some(inner="Nothing here") + True + :: + """ + ... @abstractmethod - def expect(self, msg: str) -> T: ... + def expect(self, msg: str) -> T: + """.. code-block:: python + + >>> Ok(inner=2).expect("must be positive") == 2 + True + :: + """ + ... @abstractmethod - def expect_err(self, msg: str) -> E: ... + def expect_err(self, msg: str) -> E: + """.. code-block:: python + + >>> Err(error=2, traceback="").expect_err("must be err") == 2 + True + :: + """ + ... @abstractmethod - def flatten(self) -> Result[T, E]: ... + def flatten(self) -> Result[T, E]: + """.. code-block:: python + + >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) + True + + >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) + True + + >>> Ok(inner=2).flatten() == Ok(inner=2) + True + + >>> Err(error=None, traceback="").flatten() == Err(error=None, traceback="") + True + :: + """ + ... @abstractmethod - def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: ... + def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: + """.. code-block:: python + + >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) + True + + >>> Err(error="un-appendable", traceback="").inspect(append_to_list) == Err( + ... error="un-appendable", traceback="" + ... ) + True + :: + """ + ... @abstractmethod - def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: ... + def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: + """.. code-block:: python + + >>> Err(error=[1], traceback="").inspect_err(append_to_list) == Err(error=[1], traceback="") + True + + >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") + True + :: + """ + ... @abstractmethod - def is_err(self) -> bool: ... + def is_err(self) -> bool: + """.. code-block:: python + + >>> Ok(inner=2).is_err() == False + True + + >>> Err(error=2, traceback="").is_err() == True + True + :: + """ + ... @abstractmethod - def is_err_and(self, fn: Callable[[E], bool]) -> bool: ... + def is_err_and(self, fn: Callable[[E], bool]) -> bool: + """.. code-block:: python + + >>> Err(error=1, traceback="").is_err_and(is_even) == False + True + + >>> Err(error=2, traceback="").is_err_and(is_even) == True + True + + >>> Ok(inner=2).is_err_and(is_even) == False + True + :: + """ + ... @abstractmethod - def is_ok(self) -> bool: ... + def is_ok(self) -> bool: + """.. code-block:: python + + >>> Ok(inner=2).is_ok() == True + True + + >>> Err(error=2, traceback="").is_ok() == False + True + :: + """ + ... @abstractmethod - def is_ok_and(self, fn: Callable[[T], bool]) -> bool: ... + def is_ok_and(self, fn: Callable[[T], bool]) -> bool: + """.. code-block:: python + + >>> Ok(inner=1).is_ok_and(is_even) == False + True + + >>> Ok(inner=2).is_ok_and(is_even) == True + True + + >>> Err(error=2, traceback="").is_ok_and(is_even) == False + True + :: + """ + ... @abstractmethod def map[U, **P]( self, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs - ) -> Result[U, E]: ... + ) -> Result[U, E]: + """.. code-block:: python + + >>> Ok(inner=1).map(add_one) == Ok(inner=2) + True + + >>> Err(error=1, traceback="").map(add_one) == Err(error=1, traceback="") + True + :: + """ + ... @abstractmethod def map_err[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs - ) -> Result[T, F]: ... + ) -> Result[T, F]: + """.. code-block:: python + + >>> Err(error=1, traceback="").map_err(add_one) == Err(error=2, traceback="") + True + + >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) + True + :: + """ + ... @abstractmethod def map_or[U, **P]( @@ -127,30 +301,118 @@ def map_or_else[U, **P]( ) -> U: ... @abstractmethod - def ok(self) -> Option[T]: ... + def ok(self) -> Option[T]: + """.. code-block:: python + + >>> Ok(inner=2).ok() == Some(inner=2) + True + + >>> Err(error=2, traceback="").ok() == Null() + True + :: + """ + ... @abstractmethod - def or_[F](self, res: Result[T, F]) -> Result[T, F]: ... + def or_[F](self, res: Result[T, F]) -> Result[T, F]: + """.. code-block:: python + + >>> Ok(inner=2).or_(Err(error="foo", traceback="")) == Ok(inner=2) + True + + >>> Err(error="foo", traceback="").or_(Ok(inner=100)) == Ok(inner=100) + True + + >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) + True + + >>> Err(error="foo", traceback="").or_(Err(error="foo", traceback="")) == Err( + ... error="foo", traceback="" + ... ) + True + :: + """ + ... @abstractmethod def or_else[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs - ) -> Result[T, F]: ... + ) -> Result[T, F]: + """.. code-block:: python + + >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") + True + + >>> Err(error="foo", traceback="").or_else(get_ok_vikings) == Ok(inner="vikings") + True + + >>> Err(error="foo", traceback="").or_else(Err) == Err(error="foo", traceback="") + True + :: + """ + ... @abstractmethod - def transpose(self) -> Option[Result[T, E]]: ... + def transpose(self) -> Option[Result[T, E]]: + """.. code-block:: python + + >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) + True + + >>> Ok(inner=Null()).transpose() == Null() + True + + >>> Err(error=None, traceback="").transpose() == Some(inner=Err(error=None, traceback="")) + True + :: + """ + ... @abstractmethod - def unwrap(self) -> T: ... + def unwrap(self) -> T: + """.. code-block:: python + + >>> Ok(inner=2).unwrap() == 2 + True + :: + """ + ... @abstractmethod - def unwrap_err(self) -> E: ... + def unwrap_err(self) -> E: + """.. code-block:: python + + >>> Err(error="failed", traceback="").unwrap_err() == "failed" + True + :: + """ + ... @abstractmethod - def unwrap_or(self, default: T) -> T: ... + def unwrap_or(self, default: T) -> T: + """.. code-block:: python + + >>> Ok(inner="car").unwrap_or("bike") == "car" + True + + >>> Err(error=None, traceback="").unwrap_or("bike") == "bike" + True + :: + """ + ... @abstractmethod - def unwrap_or_else(self, fn: Callable[[E], T]) -> T: ... + def unwrap_or_else(self, fn: Callable[[E], T]) -> T: + """.. code-block:: python + + >>> Ok(inner=4).unwrap_or_else(get_42) == 4 + True + + >>> Err(error=None, traceback="").unwrap_or_else(get_42) == 42 + True + :: + """ + ... @attrs.define(frozen=True) @@ -158,51 +420,193 @@ class Ok[T](Result[T, Never]): inner: T = attrs.field(default=None) def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: + """.. code-block:: python + + >>> Ok(inner=2).and_(Err(error="late error", traceback="")) == Err( + ... error="late error", traceback="" + ... ) + True + + >>> Err(error="early error", traceback="").and_(Ok(inner="foo")) == Err( + ... error="early error", traceback="" + ... ) + True + + >>> Err(error="not a 2", traceback="").and_(Err(error="late error", traceback="")) == Err( + ... error="not a 2", traceback="" + ... ) + True + + >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") + True + :: + """ return res def and_then[U, E, **P]( self, fn: Callable[Concatenate[T, P], Result[U, E]], *args: P.args, **kwargs: P.kwargs ) -> Result[U, E]: + """.. code-block:: python + + >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) + True + + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high", traceback="") + True + + >>> Err(error="not a number", traceback="").and_then(must_be_less_than_10) == Err( + ... error="not a number", traceback="" + ... ) + True + :: + """ return fn(self.inner, *args, **kwargs) def err[E](self) -> Option[E]: + """.. code-block:: python + + >>> Ok(inner=2).err() == Null() + True + + >>> Err(error="Nothing here", traceback="").err() == Some(inner="Nothing here") + True + :: + """ from ._option import Null # noqa: PLC0415 return Null() def expect(self, msg: str) -> T: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner=2).expect("must be positive") == 2 + True + :: + """ return self.inner def expect_err(self, msg: str) -> Never: + """.. code-block:: python + + >>> Err(error=2, traceback="").expect_err("must be err") == 2 + True + :: + """ raise ValueError(msg) def flatten(self) -> Result[T, Never]: + """.. code-block:: python + + >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) + True + + >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) + True + + >>> Ok(inner=2).flatten() == Ok(inner=2) + True + + >>> Err(error=None, traceback="").flatten() == Err(error=None, traceback="") + True + :: + """ if isinstance(self.inner, Result): return cast(Result[T, Never], self.inner) return cast(Result[T, Never], self) def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: + """.. code-block:: python + + >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) + True + + >>> Err(error="un-appendable", traceback="").inspect(append_to_list) == Err( + ... error="un-appendable", traceback="" + ... ) + True + :: + """ fn(deepcopy(self.inner)) return self def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: ARG002 + """.. code-block:: python + + >>> Err(error=[1], traceback="").inspect_err(append_to_list) == Err(error=[1], traceback="") + True + + >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") + True + :: + """ return self def is_err(self) -> bool: + """.. code-block:: python + + >>> Ok(inner=2).is_err() == False + True + + >>> Err(error=2, traceback="").is_err() == True + True + :: + """ return False def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 + """.. code-block:: python + + >>> Err(error=1, traceback="").is_err_and(is_even) == False + True + + >>> Err(error=2, traceback="").is_err_and(is_even) == True + True + + >>> Ok(inner=2).is_err_and(is_even) == False + True + :: + """ return False def is_ok(self) -> bool: + """.. code-block:: python + + >>> Ok(inner=2).is_ok() == True + True + + >>> Err(error=2, traceback="").is_ok() == False + True + :: + """ return True def is_ok_and(self, fn: Callable[[T], bool]) -> bool: + """.. code-block:: python + + >>> Ok(inner=1).is_ok_and(is_even) == False + True + + >>> Ok(inner=2).is_ok_and(is_even) == True + True + + >>> Err(error=2, traceback="").is_ok_and(is_even) == False + True + :: + """ return fn(self.inner) def map[U, **P]( self, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs ) -> Result[U, Never]: + """.. code-block:: python + + >>> Ok(inner=1).map(add_one) == Ok(inner=2) + True + + >>> Err(error=1, traceback="").map(add_one) == Err(error=1, traceback="") + True + :: + """ return Ok(fn(self.inner, *args, **kwargs)) def map_err[F, **P]( @@ -211,6 +615,15 @@ def map_err[F, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[T, F]: + """.. code-block:: python + + >>> Err(error=1, traceback="").map_err(add_one) == Err(error=2, traceback="") + True + + >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) + True + :: + """ return cast(Result[T, F], self) def map_or[U, **P]( @@ -232,11 +645,37 @@ def map_or_else[U, **P]( return fn(self.inner, *args, **kwargs) def ok(self) -> Option[T]: + """.. code-block:: python + + >>> Ok(inner=2).ok() == Some(inner=2) + True + + >>> Err(error=2, traceback="").ok() == Null() + True + :: + """ from ._option import Some # noqa: PLC0415 return Some(self.inner) def or_[F](self, res: Result[T, F]) -> Result[T, F]: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner=2).or_(Err(error="foo", traceback="")) == Ok(inner=2) + True + + >>> Err(error="foo", traceback="").or_(Ok(inner=100)) == Ok(inner=100) + True + + >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) + True + + >>> Err(error="foo", traceback="").or_(Err(error="foo", traceback="")) == Err( + ... error="foo", traceback="" + ... ) + True + :: + """ return cast(Result[T, F], self) def or_else[F, **P]( @@ -245,9 +684,33 @@ def or_else[F, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[T, F]: + """.. code-block:: python + + >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") + True + + >>> Err(error="foo", traceback="").or_else(get_ok_vikings) == Ok(inner="vikings") + True + + >>> Err(error="foo", traceback="").or_else(Err) == Err(error="foo", traceback="") + True + :: + """ return cast(Result[T, F], self) def transpose(self) -> Option[Result[T, Never]]: + """.. code-block:: python + + >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) + True + + >>> Ok(inner=Null()).transpose() == Null() + True + + >>> Err(error=None, traceback="").transpose() == Some(inner=Err(error=None, traceback="")) + True + :: + """ from ._option import Null, Some # noqa: PLC0415 if isinstance(self.inner, Some): @@ -257,15 +720,45 @@ def transpose(self) -> Option[Result[T, Never]]: raise TypeError("inner must be an `Option` type") def unwrap(self) -> T: + """.. code-block:: python + + >>> Ok(inner=2).unwrap() == 2 + True + :: + """ return self.inner def unwrap_err(self) -> Never: + """.. code-block:: python + + >>> Err(error="failed", traceback="").unwrap_err() == "failed" + True + :: + """ raise TypeError("Can't call `unwrap_err` on `Ok`") def unwrap_or(self, default: T) -> T: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner="car").unwrap_or("bike") == "car" + True + + >>> Err(error=None, traceback="").unwrap_or("bike") == "bike" + True + :: + """ return self.inner def unwrap_or_else(self, fn: Callable[[Never], T]) -> T: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner=4).unwrap_or_else(get_42) == 4 + True + + >>> Err(error=None, traceback="").unwrap_or_else(get_42) == 42 + True + :: + """ return self.inner @@ -282,6 +775,27 @@ class Err[E](Result[Never, E]): traceback: str = attrs.field(default="", validator=instance_of(str)) def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner=2).and_(Err(error="late error", traceback="")) == Err( + ... error="late error", traceback="" + ... ) + True + + >>> Err(error="early error", traceback="").and_(Ok(inner="foo")) == Err( + ... error="early error", traceback="" + ... ) + True + + >>> Err(error="not a 2", traceback="").and_(Err(error="late error", traceback="")) == Err( + ... error="not a 2", traceback="" + ... ) + True + + >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") + True + :: + """ return cast(Result[U, F], self) def and_then[U, F, **P]( @@ -290,39 +804,151 @@ def and_then[U, F, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[U, F]: + """.. code-block:: python + + >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) + True + + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high", traceback="") + True + + >>> Err(error="not a number", traceback="").and_then(must_be_less_than_10) == Err( + ... error="not a number", traceback="" + ... ) + True + :: + """ return cast(Result[U, F], self) def err(self) -> Option[E]: + """.. code-block:: python + + >>> Ok(inner=2).err() == Null() + True + + >>> Err(error="Nothing here", traceback="").err() == Some(inner="Nothing here") + True + :: + """ from ._option import Some # noqa: PLC0415 return Some(self.error) def expect(self, msg: str) -> Never: + """.. code-block:: python + + >>> Ok(inner=2).expect("must be positive") == 2 + True + :: + """ raise ValueError(msg) def expect_err(self, msg: str) -> E: # noqa: ARG002 + """.. code-block:: python + + >>> Err(error=2, traceback="").expect_err("must be err") == 2 + True + :: + """ return self.error def flatten(self) -> Result[Never, E]: + """.. code-block:: python + + >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) + True + + >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) + True + + >>> Ok(inner=2).flatten() == Ok(inner=2) + True + + >>> Err(error=None, traceback="").flatten() == Err(error=None, traceback="") + True + :: + """ return self def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) + True + + >>> Err(error="un-appendable", traceback="").inspect(append_to_list) == Err( + ... error="un-appendable", traceback="" + ... ) + True + :: + """ return self def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: + """.. code-block:: python + + >>> Err(error=[1], traceback="").inspect_err(append_to_list) == Err(error=[1], traceback="") + True + + >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") + True + :: + """ fn(deepcopy(self.error)) return self def is_err(self) -> bool: + """.. code-block:: python + + >>> Ok(inner=2).is_err() == False + True + + >>> Err(error=2, traceback="").is_err() == True + True + :: + """ return True def is_err_and(self, fn: Callable[[E], bool]) -> bool: + """.. code-block:: python + + >>> Err(error=1, traceback="").is_err_and(is_even) == False + True + + >>> Err(error=2, traceback="").is_err_and(is_even) == True + True + + >>> Ok(inner=2).is_err_and(is_even) == False + True + :: + """ return fn(self.error) def is_ok(self) -> bool: + """.. code-block:: python + + >>> Ok(inner=2).is_ok() == True + True + + >>> Err(error=2, traceback="").is_ok() == False + True + :: + """ return False def is_ok_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 + """.. code-block:: python + + >>> Ok(inner=1).is_ok_and(is_even) == False + True + + >>> Ok(inner=2).is_ok_and(is_even) == True + True + + >>> Err(error=2, traceback="").is_ok_and(is_even) == False + True + :: + """ return False def map[U, **P]( @@ -331,11 +957,29 @@ def map[U, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[U, E]: + """.. code-block:: python + + >>> Ok(inner=1).map(add_one) == Ok(inner=2) + True + + >>> Err(error=1, traceback="").map(add_one) == Err(error=1, traceback="") + True + :: + """ return cast(Result[U, E], self) def map_err[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs ) -> Result[Never, F]: + """.. code-block:: python + + >>> Err(error=1, traceback="").map_err(add_one) == Err(error=2, traceback="") + True + + >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) + True + :: + """ return Err( fn(self.error, *args, **kwargs), input_args=self.input_args, traceback=self.traceback ) @@ -359,33 +1003,113 @@ def map_or_else[U, **P]( return default(*args, **kwargs) def ok(self) -> Option[Never]: + """.. code-block:: python + + >>> Ok(inner=2).ok() == Some(inner=2) + True + + >>> Err(error=2, traceback="").ok() == Null() + True + :: + """ from ._option import Null # noqa: PLC0415 return Null() def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: + """.. code-block:: python + + >>> Ok(inner=2).or_(Err(error="foo", traceback="")) == Ok(inner=2) + True + + >>> Err(error="foo", traceback="").or_(Ok(inner=100)) == Ok(inner=100) + True + + >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) + True + + >>> Err(error="foo", traceback="").or_(Err(error="foo", traceback="")) == Err( + ... error="foo", traceback="" + ... ) + True + :: + """ return res def or_else[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs ) -> Result[Never, F]: + """.. code-block:: python + + >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") + True + + >>> Err(error="foo", traceback="").or_else(get_ok_vikings) == Ok(inner="vikings") + True + + >>> Err(error="foo", traceback="").or_else(Err) == Err(error="foo", traceback="") + True + :: + """ return cast(Result[Never, F], fn(self.error, *args, **kwargs)) def transpose(self) -> Option[Result[Never, E]]: + """.. code-block:: python + + >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) + True + + >>> Ok(inner=Null()).transpose() == Null() + True + + >>> Err(error=None, traceback="").transpose() == Some(inner=Err(error=None, traceback="")) + True + :: + """ from ._option import Some # noqa: PLC0415 return Some(self) def unwrap(self) -> Never: + """.. code-block:: python + + >>> Ok(inner=2).unwrap() == 2 + True + :: + """ if isinstance(self.error, Exception): raise self.error raise TypeError("Can't call `unwrap` on `Err`") def unwrap_err(self) -> E: + """.. code-block:: python + + >>> Err(error="failed", traceback="").unwrap_err() == "failed" + True + :: + """ return self.error def unwrap_or[U](self, default: U) -> U: + """.. code-block:: python + + >>> Ok(inner="car").unwrap_or("bike") == "car" + True + + >>> Err(error=None, traceback="").unwrap_or("bike") == "bike" + True + :: + """ return default def unwrap_or_else[U](self, fn: Callable[[E], U]) -> U: + """.. code-block:: python + + >>> Ok(inner=4).unwrap_or_else(get_42) == 4 + True + + >>> Err(error=None, traceback="").unwrap_or_else(get_42) == 42 + True + :: + """ return fn(self.error) diff --git a/tests/conftest.py b/tests/conftest.py index afcb898..b27e9e3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,7 @@ REPO_ROOT = Path(__file__).parents[1] -@pytest.fixture(scope="session", autouse=False) +@pytest.fixture(scope="session", autouse=True) def collect_examples() -> Generator[Any, None, None]: yield _RECORDER.path = REPO_ROOT / ".papertrail_cache/examples.json" diff --git a/uv.lock b/uv.lock index a472c94..c4ee7c4 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,11 @@ version = 1 revision = 3 requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version < '3.13'", +] [options] exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. @@ -357,6 +362,7 @@ dependencies = [ dev = [ { name = "hypothesis" }, { name = "ipykernel" }, + { name = "libcst" }, { name = "papertrail" }, { name = "pre-commit" }, { name = "pytest" }, @@ -376,6 +382,7 @@ requires-dist = [{ name = "attrs", specifier = ">=25.4.0" }] dev = [ { name = "hypothesis", specifier = ">=6.148.2" }, { name = "ipykernel", specifier = ">=7.1.0" }, + { name = "libcst", specifier = ">=1.9.0" }, { name = "papertrail", specifier = ">=0.1.2" }, { name = "pre-commit", specifier = ">=4.5.0" }, { name = "pytest", specifier = ">=9.0.1" }, @@ -624,6 +631,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, ] +[[package]] +name = "libcst" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml", marker = "python_full_version != '3.13.*'" }, + { name = "pyyaml-ft", marker = "python_full_version == '3.13.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/c0/098e5c91ff1537f00c85a6438b6cb1863d17144680cc91f47c87f104a200/libcst-1.9.0.tar.gz", hash = "sha256:087b58a9afe076bb08e2d726478e1f16cb928d67ffa9092817e033c335de522a", size = 914739, upload-time = "2026-07-29T21:28:43.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/bb/d22c37c33dfe18084634f5ef89f8f0749ffe7b6e0ad312722aafd86bbbdb/libcst-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd1a3500c41784075c4946a995d5ad89f68fa0d226b63ff3c4d78f6ea6dd23e5", size = 2043159, upload-time = "2026-07-29T19:24:49.013Z" }, + { url = "https://files.pythonhosted.org/packages/10/b8/2dedef84d72e7271119217503b69ed6dc5d0b2077685e163caae669d9c70/libcst-1.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:611cebd3bbc2014576f4dcc7b845b3c594c96ddc287a3db9b78f22eff156a7d3", size = 2203245, upload-time = "2026-07-29T19:24:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/e8/90/e02ac2dad647423f947bb11f8322bfdba8ccfdd380e6c7b695add2d1acd4/libcst-1.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8d731abe1307720ea1a52d447555e8443a6d130e0e520243c0634a58f6edbc9d", size = 2255388, upload-time = "2026-07-29T19:24:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/13/5f/6089a51518cfd2ff40950eb26bcc36951d7b6d4f4213568aa0290265aff7/libcst-1.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bc5351d92ca6ac1cc32e097700e1161ad1ceaa4d9b2cca5abadb1e94576b325", size = 2268982, upload-time = "2026-07-29T19:24:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/ed/78/26881ec466fb70cbc129dca26ccb5a52a0061face2c5822e4b61f00f9699/libcst-1.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03165a264653bb77f6a11b412ae09c08bdb0c25864f3b8d42b816ac64b9d4b9e", size = 2378174, upload-time = "2026-07-29T19:24:55.175Z" }, + { url = "https://files.pythonhosted.org/packages/e1/7a/a4dba5f11faf12a12ffba06d18019987851aab33b590242a602ffd4fb1bd/libcst-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:b755ed4a4bc2faee849b54820023137d60d4c199e0a0822f0ff0c1bc49e49b48", size = 2103462, upload-time = "2026-07-29T19:24:56.966Z" }, + { url = "https://files.pythonhosted.org/packages/f5/13/57cb129093e0d6744b3c914ba6cbbdf51ed1b2c823882936c553a3a0cf1b/libcst-1.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e50576bad7d56459d9792cd0b0dfe5469dad13646e9a9c0a8b1ba20b269f332", size = 1979825, upload-time = "2026-07-29T19:24:58.403Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b1/befc0544283bb3923a928accf79ed685e5a725524bdb3491826670affc07/libcst-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8b9df30f524317b097dc53065b25dda33d6a4cc3c7c8bf4fc83ca7559c58cc0", size = 2043754, upload-time = "2026-07-29T19:24:59.885Z" }, + { url = "https://files.pythonhosted.org/packages/45/50/fef7c172a8457c95894edf5fb04805024899cdfea41fa01b0636587b79e1/libcst-1.9.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e465a7bc9c2b9533eb9e06d2391f8819f811b5112919d4064c9fb8565aaafa08", size = 2203548, upload-time = "2026-07-29T19:25:01.323Z" }, + { url = "https://files.pythonhosted.org/packages/18/ff/764cd2be1fd99d774fc44039c319dc0ed1d9d9afeaa02759a05121cccd4b/libcst-1.9.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8504b422c95676a8c27b517e1ac01413ece91bf356865c587ca9bdcd5708a2f7", size = 2255274, upload-time = "2026-07-29T19:25:02.764Z" }, + { url = "https://files.pythonhosted.org/packages/34/a7/474748a27a02fa83e3556b260d5f5236ca48164fa7beffecf3b2cad24dca/libcst-1.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bcb9f9d4fcfe2ec7a40d2c26e03a538d5d9dc189c38551eb3f94ab661afee7c0", size = 2269126, upload-time = "2026-07-29T19:25:04.158Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b7/655e45363b8cf87b91e41e060b21c89e5316c7ef36eb14a1a498b27bb71e/libcst-1.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8d671c39a431c309476099b8ec811e412503ec0f4465f6fc907cb51c70e8e6b", size = 2378602, upload-time = "2026-07-29T19:25:06.424Z" }, + { url = "https://files.pythonhosted.org/packages/db/13/6da63f0902ece43bf9d737017251ad7ad06edd9d3c4e1856450403cb4473/libcst-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:4d382fba04077eb556a1ea4295a4482e192aa93639c5a07ba0885841965ea0c0", size = 2103486, upload-time = "2026-07-29T19:25:07.979Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ff/dccb1a55e38b4e47256a97242d61d2bef5366c744faf88fb087d4fa0f995/libcst-1.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:a621e261990148c1cfbe26c1798bd2375c131cf358a44a7a4659fc41c1a336e1", size = 1979907, upload-time = "2026-07-29T19:25:09.532Z" }, + { url = "https://files.pythonhosted.org/packages/65/2a/4943c71d90975bc59034a057dea346b365c276f308c1d31f5cf2bd85492d/libcst-1.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:eccf4c57d273cdd3fe1c67b72cf9bb1bbd4547aa011824e96ccf5b7136057aa4", size = 2044529, upload-time = "2026-07-29T19:25:11.04Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1168b98c2a0f338be3a44753647baab051feaf6167cc887bf4447f8fd920/libcst-1.9.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32395244edfe6538e0ea2bf82051d60103d3f54861274805c4fb3745efb70a85", size = 2203519, upload-time = "2026-07-29T19:25:12.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/7d/2eaa697a80f899bcf2245680bbfcc1e478eb3b3328c21d4b2880bdf00dac/libcst-1.9.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:444e84c76cd035cd2fe136838c1a524d34b08216521f6f5093df8a6f6cfa5799", size = 2255879, upload-time = "2026-07-29T19:25:14.137Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/793b9fd96dd52d202f5f2b88d7e54f05ebed767d1bb3b32395a1817bf6ab/libcst-1.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb5d0946f2b4c6711b5d69fe4f833b364f9e7a1a2b08f88b98619dc18975099a", size = 2269807, upload-time = "2026-07-29T19:25:15.647Z" }, + { url = "https://files.pythonhosted.org/packages/a3/f4/1bc7aaea03971c45e8a885fed9fc1c73176dbbd00b0296a3cde9529bafd6/libcst-1.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:45808c03528b3ad40b14095348a918e08d98c47b4a125d631cb78e817c0a5b16", size = 2378171, upload-time = "2026-07-29T19:25:17.289Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f7/bc49e367d2bc8817213594dd52cf6b2da2dbbda90b5382d60653999274ac/libcst-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:568288cdbfe3b4ca3ae4852cb0a439ff053dd54c841bc4995bf2b71238b5de40", size = 2177847, upload-time = "2026-07-29T19:25:19.35Z" }, + { url = "https://files.pythonhosted.org/packages/87/80/4d81577a22e6d535d1a3409f3a1c6903e09036f0e17fc47f92169dbc3501/libcst-1.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:107593af46945593e7825821793393262bc4fa1d3ea24c3ed487b61269bbbdf8", size = 2058134, upload-time = "2026-07-29T19:25:20.769Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7f/c3f3a0e7a1a2adaa76e815e82a7814d6bfe7d49432276bba652248c68d0b/libcst-1.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f6248cb07444ab9a6733a855737a9febed8b9adca51347019348b09a3ac7dfe9", size = 2036102, upload-time = "2026-07-29T19:25:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/4e/af/2f5543255b2c7d749b966adeccc6bfb1652cf8b425afb913d0f3f025a865/libcst-1.9.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:496c24e0d3240bc7da45dae543aff3f5b6509978c39262d6b84ed2fb999dded2", size = 2194806, upload-time = "2026-07-29T19:25:24.017Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5b/03f4cddce426d005e208b39ea7b2d456e667cdee0f1891360f0fc8430f20/libcst-1.9.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ea490fa8540503db5f321f0268becab46eb50f710e8cec8041e241fd66f6874f", size = 2246762, upload-time = "2026-07-29T19:25:25.365Z" }, + { url = "https://files.pythonhosted.org/packages/d8/31/9d5fe1e43dc3dbcc74f70f3e0e73fcdd8d84effc1059d8b45974f0d0d2eb/libcst-1.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:50ab94bb2524b419056d4003032b8c67102ac800f8d85b3c4260a01746d94dbb", size = 2259633, upload-time = "2026-07-29T19:25:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2b/6752b28d88c3a19b3bc0b9e1838443b6760d5c862b4f4b37955402659e24/libcst-1.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a2faaf92500d0226358125630f5aab4758e8aad3f2d70a10892ec3c700781a54", size = 2368043, upload-time = "2026-07-29T19:25:28.344Z" }, + { url = "https://files.pythonhosted.org/packages/e2/94/775825b2637f8ab05694b6a4b3802ae6783b4e799f9b58d2400c7e2d4369/libcst-1.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0c7b548512db25af9c2997a95fa731bd6b6928ecbad6c0915d7482d8bb42d34f", size = 2176432, upload-time = "2026-07-29T19:25:29.921Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3d/88ad67427c6fd9db929e087912b0a540e5140e5cb77e7ca4170edaac8531/libcst-1.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:497d5329345f1f5df84e41b0bbd00204b64a2fd30dfe3cfaeaebca633a31e877", size = 2052931, upload-time = "2026-07-29T19:25:31.397Z" }, + { url = "https://files.pythonhosted.org/packages/39/e4/ad790b043a38b10cea13166c8dd716654ad8b227c20f12eebf6326191cd9/libcst-1.9.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:a5068bf6114f6f4d79af7a6c80a28d1deb50441150ea1db13b5458ab34bec159", size = 2043987, upload-time = "2026-08-11T05:54:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/15/6b/d3cd8275cc54ffc9817ade91442b3e6e9edd1a4518bd4e6dda13e3152dbe/libcst-1.9.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:7acfd18adcdd32dcf41ce676cf121002afcbe9ad2c72dc0c18d78465beedc228", size = 2204003, upload-time = "2026-08-11T05:54:04.031Z" }, + { url = "https://files.pythonhosted.org/packages/da/62/87eddccb11d5d221d50ac4fff4b6e9b8d48c547028d01f7d0fbc5c8a1d75/libcst-1.9.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:86361e2b426bd1b403e52375703c8b764e226e571adcb30442510710681edbf0", size = 2256053, upload-time = "2026-08-11T05:54:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/a44126aeb9fca8b4e342e0cc803ea00ca8f6cd5712619a7897b3eba13797/libcst-1.9.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8c14abe844bec8b021111b3985474e49f5af799c020e8e40dedcac87c97a40c5", size = 2270576, upload-time = "2026-08-11T05:54:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/bb/51/3af3dd44b117d66486e0ed61e43a16b7fc8cc5c372e1dd4ca0e70b888d46/libcst-1.9.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:8930971d2299b7006bb5d10c10038f44efb6da89d5bca2823595e31a9cdb96af", size = 2378573, upload-time = "2026-08-11T05:54:10.093Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e8/e545087d298dbf6494e84a8092f0f5dbd62eedacabd4ecd6b364367b4804/libcst-1.9.0-cp315-cp315-win_amd64.whl", hash = "sha256:5891ce9cff815077614f509e3a23888c5ddb8081d6188e8ba1172c0ccc369022", size = 2177937, upload-time = "2026-08-11T05:54:12.168Z" }, + { url = "https://files.pythonhosted.org/packages/84/17/93a00a8e03494a84db102dd0e3d35b8876b1084ca2c194b331a168d86eb1/libcst-1.9.0-cp315-cp315-win_arm64.whl", hash = "sha256:1260b5d070a3324447a53a00387225a55472a62077ce01922d30a2a78cc4b138", size = 2058633, upload-time = "2026-08-11T05:54:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/d7/00/c3751b12eb81822ea0b6131d374a98bc6c024c4b9ec5f6ea8f53dc23e967/libcst-1.9.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:02ee2dbdb5c218116f16a021350fc267a14be205b50d81c33f5d73857ab6fbf7", size = 2036178, upload-time = "2026-08-11T05:54:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/2f/12/48ff486eb5adc593f4818569e8896b74b672414ab4722a74b17e4c4cf31e/libcst-1.9.0-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:3e5b684191a0462b2d40a73261ea7f4da6a49e7a030b7e0fceca46bebb51dfe5", size = 2195496, upload-time = "2026-08-11T05:54:17.625Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/b13a4669864d41a4801fed7b86ede1049dc9a1e1dde250a7ee1f9c3b1285/libcst-1.9.0-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:2b150c4f298fe54eb0fe73abf71f57db74d070796140a8c206c9615b6861f01e", size = 2245769, upload-time = "2026-08-11T05:54:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c7/cf2d46744825e84afbd7228fdeeea8d23cf6c4b2074253c27efb7705c653/libcst-1.9.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:50b913e37187f00a6fb88962009364e1cf1b1284aa1762304f34b52b972f2218", size = 2260576, upload-time = "2026-08-11T05:54:21.444Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/5433d3d62250b2e4325938db101766bcab2a006819684713bcccb6259a88/libcst-1.9.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:9f92c75283030fd58fb7d6e560168a00381e0e3368ec8a026a3ec8a828a05f93", size = 2368709, upload-time = "2026-08-11T05:54:23.276Z" }, + { url = "https://files.pythonhosted.org/packages/87/39/9f0e1690727623f99489895db0e42048a3e1e8cea0627517c593e437fd83/libcst-1.9.0-cp315-cp315t-win_amd64.whl", hash = "sha256:4adf97bb1aff8039b0bd4991e2f838be6e4fad552821b26b71a5d1a653c2582f", size = 2177866, upload-time = "2026-08-11T05:54:25.144Z" }, + { url = "https://files.pythonhosted.org/packages/cd/79/9dc7811883e67ea771057e8937bc536eb3e77f3635fc5a93cd85b6fe1ea8/libcst-1.9.0-cp315-cp315t-win_arm64.whl", hash = "sha256:8f0dd08a5773d7051e105250b2a2c73d8065ea9b2d68e7e1bdc5244660e75f93", size = 2052908, upload-time = "2026-08-11T05:54:26.835Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -1086,6 +1147,30 @@ wheels = [ { 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-ft" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/eb/5a0d575de784f9a1f94e2b1288c6886f13f34185e13117ed530f32b6f8a8/pyyaml_ft-8.0.0.tar.gz", hash = "sha256:0c947dce03954c7b5d38869ed4878b2e6ff1d44b08a0d84dc83fdad205ae39ab", size = 141057, upload-time = "2025-06-10T15:32:15.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/ba/a067369fe61a2e57fb38732562927d5bae088c73cb9bb5438736a9555b29/pyyaml_ft-8.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c1306282bc958bfda31237f900eb52c9bedf9b93a11f82e1aab004c9a5657a6", size = 187027, upload-time = "2025-06-10T15:31:48.722Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c5/a3d2020ce5ccfc6aede0d45bcb870298652ac0cf199f67714d250e0cdf39/pyyaml_ft-8.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30c5f1751625786c19de751e3130fc345ebcba6a86f6bddd6e1285342f4bbb69", size = 176146, upload-time = "2025-06-10T15:31:50.584Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bb/23a9739291086ca0d3189eac7cd92b4d00e9fdc77d722ab610c35f9a82ba/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa992481155ddda2e303fcc74c79c05eddcdbc907b888d3d9ce3ff3e2adcfb0", size = 746792, upload-time = "2025-06-10T15:31:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c2/e8825f4ff725b7e560d62a3609e31d735318068e1079539ebfde397ea03e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cec6c92b4207004b62dfad1f0be321c9f04725e0f271c16247d8b39c3bf3ea42", size = 786772, upload-time = "2025-06-10T15:31:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/35/be/58a4dcae8854f2fdca9b28d9495298fd5571a50d8430b1c3033ec95d2d0e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06237267dbcab70d4c0e9436d8f719f04a51123f0ca2694c00dd4b68c338e40b", size = 778723, upload-time = "2025-06-10T15:31:56.093Z" }, + { url = "https://files.pythonhosted.org/packages/86/ed/fed0da92b5d5d7340a082e3802d84c6dc9d5fa142954404c41a544c1cb92/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a7f332bc565817644cdb38ffe4739e44c3e18c55793f75dddb87630f03fc254", size = 758478, upload-time = "2025-06-10T15:31:58.314Z" }, + { url = "https://files.pythonhosted.org/packages/f0/69/ac02afe286275980ecb2dcdc0156617389b7e0c0a3fcdedf155c67be2b80/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d10175a746be65f6feb86224df5d6bc5c049ebf52b89a88cf1cd78af5a367a8", size = 799159, upload-time = "2025-06-10T15:31:59.675Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ac/c492a9da2e39abdff4c3094ec54acac9747743f36428281fb186a03fab76/pyyaml_ft-8.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:58e1015098cf8d8aec82f360789c16283b88ca670fe4275ef6c48c5e30b22a96", size = 158779, upload-time = "2025-06-10T15:32:01.029Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9b/41998df3298960d7c67653669f37710fa2d568a5fc933ea24a6df60acaf6/pyyaml_ft-8.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5f3e2ceb790d50602b2fd4ec37abbd760a8c778e46354df647e7c5a4ebb", size = 191331, upload-time = "2025-06-10T15:32:02.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/16/2710c252ee04cbd74d9562ebba709e5a284faeb8ada88fcda548c9191b47/pyyaml_ft-8.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d445bf6ea16bb93c37b42fdacfb2f94c8e92a79ba9e12768c96ecde867046d1", size = 182879, upload-time = "2025-06-10T15:32:04.466Z" }, + { url = "https://files.pythonhosted.org/packages/9a/40/ae8163519d937fa7bfa457b6f78439cc6831a7c2b170e4f612f7eda71815/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c56bb46b4fda34cbb92a9446a841da3982cdde6ea13de3fbd80db7eeeab8b49", size = 811277, upload-time = "2025-06-10T15:32:06.214Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/28d82dbff7f87b96f0eeac79b7d972a96b4980c1e445eb6a857ba91eda00/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dab0abb46eb1780da486f022dce034b952c8ae40753627b27a626d803926483b", size = 831650, upload-time = "2025-06-10T15:32:08.076Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/161c4566facac7d75a9e182295c223060373d4116dead9cc53a265de60b9/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd48d639cab5ca50ad957b6dd632c7dd3ac02a1abe0e8196a3c24a52f5db3f7a", size = 815755, upload-time = "2025-06-10T15:32:09.435Z" }, + { url = "https://files.pythonhosted.org/packages/05/10/f42c48fa5153204f42eaa945e8d1fd7c10d6296841dcb2447bf7da1be5c4/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:052561b89d5b2a8e1289f326d060e794c21fa068aa11255fe71d65baf18a632e", size = 810403, upload-time = "2025-06-10T15:32:11.051Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d2/e369064aa51009eb9245399fd8ad2c562bd0bcd392a00be44b2a824ded7c/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3bb4b927929b0cb162fb1605392a321e3333e48ce616cdcfa04a839271373255", size = 835581, upload-time = "2025-06-10T15:32:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/c0/28/26534bed77109632a956977f60d8519049f545abc39215d086e33a61f1f2/pyyaml_ft-8.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:de04cfe9439565e32f178106c51dd6ca61afaa2907d143835d501d84703d3793", size = 171579, upload-time = "2025-06-10T15:32:14.34Z" }, +] + [[package]] name = "pyzmq" version = "27.1.0" From 3d9dd43399dba9cf874682bb605e2a744098869e Mon Sep 17 00:00:00 2001 From: ed cuss Date: Fri, 25 Sep 2026 21:18:20 +0100 Subject: [PATCH 05/11] fix: handle builtin make traceback not part of the repr --- .../create_examples/collection/record.py | 2 + src/danom/_monads/_option.py | 84 ++++-- src/danom/_monads/_result_v2.py | 262 ++++++++++-------- tests/monads/test_option.py | 4 +- tests/monads/test_result_v2.py | 4 +- 5 files changed, 214 insertions(+), 142 deletions(-) diff --git a/dev_tools/create_examples/collection/record.py b/dev_tools/create_examples/collection/record.py index 7c7a231..5add869 100644 --- a/dev_tools/create_examples/collection/record.py +++ b/dev_tools/create_examples/collection/record.py @@ -56,4 +56,6 @@ def _clean_repr(raw: str) -> str: return raw.removeprefix("").split(".")[-1] + if raw.startswith("") return raw diff --git a/src/danom/_monads/_option.py b/src/danom/_monads/_option.py index 793f390..f86d033 100644 --- a/src/danom/_monads/_option.py +++ b/src/danom/_monads/_option.py @@ -216,10 +216,30 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: ... @abstractmethod - def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: ... + def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: + """.. code-block:: python + + >>> Some(inner="foo").map_or(42, len) == 3 + True + + >>> Null().map_or(42, len) == 42 + True + :: + """ + ... @abstractmethod - def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: ... + def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: + """.. code-block:: python + + >>> Some(inner="foo").map_or_else(get_42, len) == 3 + True + + >>> Null().map_or_else(get_42, len) == 42 + True + :: + """ + ... @abstractmethod def ok_or[E](self, err: E) -> Result[T, E]: @@ -228,7 +248,7 @@ def ok_or[E](self, err: E) -> Result[T, E]: >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True - >>> Null().ok_or(0) == Err(error=0, traceback="") + >>> Null().ok_or(0) == Err(error=0) True :: """ @@ -241,7 +261,7 @@ def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True - >>> Null().ok_or_else(get_42) == Err(error=42, traceback="") + >>> Null().ok_or_else(get_42) == Err(error=42) True :: """ @@ -302,9 +322,7 @@ def transpose[E](self) -> Result[Option[T], E]: >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True - >>> Some(inner=Err(error=2, traceback="")).transpose() == Err( - ... error=Some(inner=2), traceback="" - ... ) + >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True >>> Null().transpose() == Ok(inner=Null()) @@ -567,9 +585,27 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: return Some(fn(self.inner)) def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="foo").map_or(42, len) == 3 + True + + >>> Null().map_or(42, len) == 42 + True + :: + """ return fn(self.inner) def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="foo").map_or_else(get_42, len) == 3 + True + + >>> Null().map_or_else(get_42, len) == 42 + True + :: + """ return fn(self.inner) def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 @@ -578,7 +614,7 @@ def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True - >>> Null().ok_or(0) == Err(error=0, traceback="") + >>> Null().ok_or(0) == Err(error=0) True :: """ @@ -592,7 +628,7 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True - >>> Null().ok_or_else(get_42) == Err(error=42, traceback="") + >>> Null().ok_or_else(get_42) == Err(error=42) True :: """ @@ -651,9 +687,7 @@ def transpose(self) -> Result[Option[T], Option[T]]: >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True - >>> Some(inner=Err(error=2, traceback="")).transpose() == Err( - ... error=Some(inner=2), traceback="" - ... ) + >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True >>> Null().transpose() == Ok(inner=Null()) @@ -916,9 +950,27 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 return self def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="foo").map_or(42, len) == 3 + True + + >>> Null().map_or(42, len) == 42 + True + :: + """ return default def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: # noqa: ARG002 + """.. code-block:: python + + >>> Some(inner="foo").map_or_else(get_42, len) == 3 + True + + >>> Null().map_or_else(get_42, len) == 42 + True + :: + """ return default() def ok_or[E](self, err: E) -> Result[T, E]: @@ -927,7 +979,7 @@ def ok_or[E](self, err: E) -> Result[T, E]: >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True - >>> Null().ok_or(0) == Err(error=0, traceback="") + >>> Null().ok_or(0) == Err(error=0) True :: """ @@ -941,7 +993,7 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True - >>> Null().ok_or_else(get_42) == Err(error=42, traceback="") + >>> Null().ok_or_else(get_42) == Err(error=42) True :: """ @@ -1000,9 +1052,7 @@ def transpose[E](self) -> Result[Option[T], E]: >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True - >>> Some(inner=Err(error=2, traceback="")).transpose() == Err( - ... error=Some(inner=2), traceback="" - ... ) + >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True >>> Null().transpose() == Ok(inner=Null()) diff --git a/src/danom/_monads/_result_v2.py b/src/danom/_monads/_result_v2.py index 21c59cb..9782b88 100644 --- a/src/danom/_monads/_result_v2.py +++ b/src/danom/_monads/_result_v2.py @@ -65,19 +65,13 @@ def result_unwrap(result: Result[T, E]) -> T: def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: """.. code-block:: python - >>> Ok(inner=2).and_(Err(error="late error", traceback="")) == Err( - ... error="late error", traceback="" - ... ) + >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - >>> Err(error="early error", traceback="").and_(Ok(inner="foo")) == Err( - ... error="early error", traceback="" - ... ) + >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - >>> Err(error="not a 2", traceback="").and_(Err(error="late error", traceback="")) == Err( - ... error="not a 2", traceback="" - ... ) + >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") @@ -95,12 +89,10 @@ def and_then[U, F, **P]( >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high", traceback="") + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - >>> Err(error="not a number", traceback="").and_then(must_be_less_than_10) == Err( - ... error="not a number", traceback="" - ... ) + >>> Err(error="not a number").and_then(must_be_less_than_10) == Err(error="not a number") True :: """ @@ -112,7 +104,7 @@ def cloned(self) -> Result[T, E]: >>> Ok(inner=2).cloned() == Ok(inner=2) True - >>> Err(error=2, traceback="").cloned() == Err(error=2, traceback="") + >>> Err(error=2).cloned() == Err(error=2) True :: """ @@ -125,7 +117,7 @@ def err(self) -> Option[E]: >>> Ok(inner=2).err() == Null() True - >>> Err(error="Nothing here", traceback="").err() == Some(inner="Nothing here") + >>> Err(error="Nothing here").err() == Some(inner="Nothing here") True :: """ @@ -145,7 +137,7 @@ def expect(self, msg: str) -> T: def expect_err(self, msg: str) -> E: """.. code-block:: python - >>> Err(error=2, traceback="").expect_err("must be err") == 2 + >>> Err(error=2).expect_err("must be err") == 2 True :: """ @@ -164,7 +156,7 @@ def flatten(self) -> Result[T, E]: >>> Ok(inner=2).flatten() == Ok(inner=2) True - >>> Err(error=None, traceback="").flatten() == Err(error=None, traceback="") + >>> Err(error=None).flatten() == Err(error=None) True :: """ @@ -177,9 +169,7 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - >>> Err(error="un-appendable", traceback="").inspect(append_to_list) == Err( - ... error="un-appendable", traceback="" - ... ) + >>> Err(error="un-appendable").inspect(append_to_list) == Err(error="un-appendable") True :: """ @@ -189,7 +179,7 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: """.. code-block:: python - >>> Err(error=[1], traceback="").inspect_err(append_to_list) == Err(error=[1], traceback="") + >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") @@ -205,7 +195,7 @@ def is_err(self) -> bool: >>> Ok(inner=2).is_err() == False True - >>> Err(error=2, traceback="").is_err() == True + >>> Err(error=2).is_err() == True True :: """ @@ -215,10 +205,10 @@ def is_err(self) -> bool: def is_err_and(self, fn: Callable[[E], bool]) -> bool: """.. code-block:: python - >>> Err(error=1, traceback="").is_err_and(is_even) == False + >>> Err(error=1).is_err_and(is_even) == False True - >>> Err(error=2, traceback="").is_err_and(is_even) == True + >>> Err(error=2).is_err_and(is_even) == True True >>> Ok(inner=2).is_err_and(is_even) == False @@ -234,7 +224,7 @@ def is_ok(self) -> bool: >>> Ok(inner=2).is_ok() == True True - >>> Err(error=2, traceback="").is_ok() == False + >>> Err(error=2).is_ok() == False True :: """ @@ -250,7 +240,7 @@ def is_ok_and(self, fn: Callable[[T], bool]) -> bool: >>> Ok(inner=2).is_ok_and(is_even) == True True - >>> Err(error=2, traceback="").is_ok_and(is_even) == False + >>> Err(error=2).is_ok_and(is_even) == False True :: """ @@ -265,7 +255,7 @@ def map[U, **P]( >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - >>> Err(error=1, traceback="").map(add_one) == Err(error=1, traceback="") + >>> Err(error=1).map(add_one) == Err(error=1) True :: """ @@ -277,7 +267,7 @@ def map_err[F, **P]( ) -> Result[T, F]: """.. code-block:: python - >>> Err(error=1, traceback="").map_err(add_one) == Err(error=2, traceback="") + >>> Err(error=1).map_err(add_one) == Err(error=2) True >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) @@ -289,7 +279,17 @@ def map_err[F, **P]( @abstractmethod def map_or[U, **P]( self, default: U, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs - ) -> U: ... + ) -> U: + """.. code-block:: python + + >>> Ok(inner="foo").map_or(42, len) == 3 + True + + >>> Err(error=None).map_or(42, len) == 42 + True + :: + """ + ... @abstractmethod def map_or_else[U, **P]( @@ -298,7 +298,17 @@ def map_or_else[U, **P]( fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs, - ) -> U: ... + ) -> U: + """.. code-block:: python + + >>> Ok(inner="foo").map_or_else(get_42, len) == 3 + True + + >>> Err(error=None).map_or_else(get_42, len) == 42 + True + :: + """ + ... @abstractmethod def ok(self) -> Option[T]: @@ -307,7 +317,7 @@ def ok(self) -> Option[T]: >>> Ok(inner=2).ok() == Some(inner=2) True - >>> Err(error=2, traceback="").ok() == Null() + >>> Err(error=2).ok() == Null() True :: """ @@ -317,18 +327,16 @@ def ok(self) -> Option[T]: def or_[F](self, res: Result[T, F]) -> Result[T, F]: """.. code-block:: python - >>> Ok(inner=2).or_(Err(error="foo", traceback="")) == Ok(inner=2) + >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - >>> Err(error="foo", traceback="").or_(Ok(inner=100)) == Ok(inner=100) + >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - >>> Err(error="foo", traceback="").or_(Err(error="foo", traceback="")) == Err( - ... error="foo", traceback="" - ... ) + >>> Err(error="foo").or_(Err(error="foo")) == Err(error="foo") True :: """ @@ -343,10 +351,10 @@ def or_else[F, **P]( >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - >>> Err(error="foo", traceback="").or_else(get_ok_vikings) == Ok(inner="vikings") + >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - >>> Err(error="foo", traceback="").or_else(Err) == Err(error="foo", traceback="") + >>> Err(error="foo").or_else(Err) == Err(error="foo") True :: """ @@ -362,7 +370,7 @@ def transpose(self) -> Option[Result[T, E]]: >>> Ok(inner=Null()).transpose() == Null() True - >>> Err(error=None, traceback="").transpose() == Some(inner=Err(error=None, traceback="")) + >>> Err(error=None).transpose() == Some(inner=Err(error=None)) True :: """ @@ -382,7 +390,7 @@ def unwrap(self) -> T: def unwrap_err(self) -> E: """.. code-block:: python - >>> Err(error="failed", traceback="").unwrap_err() == "failed" + >>> Err(error="failed").unwrap_err() == "failed" True :: """ @@ -395,7 +403,7 @@ def unwrap_or(self, default: T) -> T: >>> Ok(inner="car").unwrap_or("bike") == "car" True - >>> Err(error=None, traceback="").unwrap_or("bike") == "bike" + >>> Err(error=None).unwrap_or("bike") == "bike" True :: """ @@ -408,7 +416,7 @@ def unwrap_or_else(self, fn: Callable[[E], T]) -> T: >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - >>> Err(error=None, traceback="").unwrap_or_else(get_42) == 42 + >>> Err(error=None).unwrap_or_else(get_42) == 42 True :: """ @@ -422,19 +430,13 @@ class Ok[T](Result[T, Never]): def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: """.. code-block:: python - >>> Ok(inner=2).and_(Err(error="late error", traceback="")) == Err( - ... error="late error", traceback="" - ... ) + >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - >>> Err(error="early error", traceback="").and_(Ok(inner="foo")) == Err( - ... error="early error", traceback="" - ... ) + >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - >>> Err(error="not a 2", traceback="").and_(Err(error="late error", traceback="")) == Err( - ... error="not a 2", traceback="" - ... ) + >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") @@ -451,12 +453,10 @@ def and_then[U, E, **P]( >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high", traceback="") + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - >>> Err(error="not a number", traceback="").and_then(must_be_less_than_10) == Err( - ... error="not a number", traceback="" - ... ) + >>> Err(error="not a number").and_then(must_be_less_than_10) == Err(error="not a number") True :: """ @@ -468,7 +468,7 @@ def err[E](self) -> Option[E]: >>> Ok(inner=2).err() == Null() True - >>> Err(error="Nothing here", traceback="").err() == Some(inner="Nothing here") + >>> Err(error="Nothing here").err() == Some(inner="Nothing here") True :: """ @@ -488,7 +488,7 @@ def expect(self, msg: str) -> T: # noqa: ARG002 def expect_err(self, msg: str) -> Never: """.. code-block:: python - >>> Err(error=2, traceback="").expect_err("must be err") == 2 + >>> Err(error=2).expect_err("must be err") == 2 True :: """ @@ -506,7 +506,7 @@ def flatten(self) -> Result[T, Never]: >>> Ok(inner=2).flatten() == Ok(inner=2) True - >>> Err(error=None, traceback="").flatten() == Err(error=None, traceback="") + >>> Err(error=None).flatten() == Err(error=None) True :: """ @@ -520,9 +520,7 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - >>> Err(error="un-appendable", traceback="").inspect(append_to_list) == Err( - ... error="un-appendable", traceback="" - ... ) + >>> Err(error="un-appendable").inspect(append_to_list) == Err(error="un-appendable") True :: """ @@ -532,7 +530,7 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: ARG002 """.. code-block:: python - >>> Err(error=[1], traceback="").inspect_err(append_to_list) == Err(error=[1], traceback="") + >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") @@ -547,7 +545,7 @@ def is_err(self) -> bool: >>> Ok(inner=2).is_err() == False True - >>> Err(error=2, traceback="").is_err() == True + >>> Err(error=2).is_err() == True True :: """ @@ -556,10 +554,10 @@ def is_err(self) -> bool: def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 """.. code-block:: python - >>> Err(error=1, traceback="").is_err_and(is_even) == False + >>> Err(error=1).is_err_and(is_even) == False True - >>> Err(error=2, traceback="").is_err_and(is_even) == True + >>> Err(error=2).is_err_and(is_even) == True True >>> Ok(inner=2).is_err_and(is_even) == False @@ -574,7 +572,7 @@ def is_ok(self) -> bool: >>> Ok(inner=2).is_ok() == True True - >>> Err(error=2, traceback="").is_ok() == False + >>> Err(error=2).is_ok() == False True :: """ @@ -589,7 +587,7 @@ def is_ok_and(self, fn: Callable[[T], bool]) -> bool: >>> Ok(inner=2).is_ok_and(is_even) == True True - >>> Err(error=2, traceback="").is_ok_and(is_even) == False + >>> Err(error=2).is_ok_and(is_even) == False True :: """ @@ -603,7 +601,7 @@ def map[U, **P]( >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - >>> Err(error=1, traceback="").map(add_one) == Err(error=1, traceback="") + >>> Err(error=1).map(add_one) == Err(error=1) True :: """ @@ -617,7 +615,7 @@ def map_err[F, **P]( ) -> Result[T, F]: """.. code-block:: python - >>> Err(error=1, traceback="").map_err(add_one) == Err(error=2, traceback="") + >>> Err(error=1).map_err(add_one) == Err(error=2) True >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) @@ -633,6 +631,15 @@ def map_or[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: + """.. code-block:: python + + >>> Ok(inner="foo").map_or(42, len) == 3 + True + + >>> Err(error=None).map_or(42, len) == 42 + True + :: + """ return fn(self.inner, *args, **kwargs) def map_or_else[U, **P]( @@ -642,6 +649,15 @@ def map_or_else[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: + """.. code-block:: python + + >>> Ok(inner="foo").map_or_else(get_42, len) == 3 + True + + >>> Err(error=None).map_or_else(get_42, len) == 42 + True + :: + """ return fn(self.inner, *args, **kwargs) def ok(self) -> Option[T]: @@ -650,7 +666,7 @@ def ok(self) -> Option[T]: >>> Ok(inner=2).ok() == Some(inner=2) True - >>> Err(error=2, traceback="").ok() == Null() + >>> Err(error=2).ok() == Null() True :: """ @@ -661,18 +677,16 @@ def ok(self) -> Option[T]: def or_[F](self, res: Result[T, F]) -> Result[T, F]: # noqa: ARG002 """.. code-block:: python - >>> Ok(inner=2).or_(Err(error="foo", traceback="")) == Ok(inner=2) + >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - >>> Err(error="foo", traceback="").or_(Ok(inner=100)) == Ok(inner=100) + >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - >>> Err(error="foo", traceback="").or_(Err(error="foo", traceback="")) == Err( - ... error="foo", traceback="" - ... ) + >>> Err(error="foo").or_(Err(error="foo")) == Err(error="foo") True :: """ @@ -689,10 +703,10 @@ def or_else[F, **P]( >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - >>> Err(error="foo", traceback="").or_else(get_ok_vikings) == Ok(inner="vikings") + >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - >>> Err(error="foo", traceback="").or_else(Err) == Err(error="foo", traceback="") + >>> Err(error="foo").or_else(Err) == Err(error="foo") True :: """ @@ -707,7 +721,7 @@ def transpose(self) -> Option[Result[T, Never]]: >>> Ok(inner=Null()).transpose() == Null() True - >>> Err(error=None, traceback="").transpose() == Some(inner=Err(error=None, traceback="")) + >>> Err(error=None).transpose() == Some(inner=Err(error=None)) True :: """ @@ -731,7 +745,7 @@ def unwrap(self) -> T: def unwrap_err(self) -> Never: """.. code-block:: python - >>> Err(error="failed", traceback="").unwrap_err() == "failed" + >>> Err(error="failed").unwrap_err() == "failed" True :: """ @@ -743,7 +757,7 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 >>> Ok(inner="car").unwrap_or("bike") == "car" True - >>> Err(error=None, traceback="").unwrap_or("bike") == "bike" + >>> Err(error=None).unwrap_or("bike") == "bike" True :: """ @@ -755,7 +769,7 @@ def unwrap_or_else(self, fn: Callable[[Never], T]) -> T: # noqa: ARG002 >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - >>> Err(error=None, traceback="").unwrap_or_else(get_42) == 42 + >>> Err(error=None).unwrap_or_else(get_42) == 42 True :: """ @@ -772,24 +786,18 @@ class Err[E](Result[Never, E]): input_args: tuple[()] | SafeArgs | SafeMethodArgs = attrs.field( default=(), validator=instance_of(tuple), repr=False ) - traceback: str = attrs.field(default="", validator=instance_of(str)) + traceback: str = attrs.field(default="", validator=instance_of(str), repr=False) def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: # noqa: ARG002 """.. code-block:: python - >>> Ok(inner=2).and_(Err(error="late error", traceback="")) == Err( - ... error="late error", traceback="" - ... ) + >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - >>> Err(error="early error", traceback="").and_(Ok(inner="foo")) == Err( - ... error="early error", traceback="" - ... ) + >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - >>> Err(error="not a 2", traceback="").and_(Err(error="late error", traceback="")) == Err( - ... error="not a 2", traceback="" - ... ) + >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") @@ -809,12 +817,10 @@ def and_then[U, F, **P]( >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high", traceback="") + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - >>> Err(error="not a number", traceback="").and_then(must_be_less_than_10) == Err( - ... error="not a number", traceback="" - ... ) + >>> Err(error="not a number").and_then(must_be_less_than_10) == Err(error="not a number") True :: """ @@ -826,7 +832,7 @@ def err(self) -> Option[E]: >>> Ok(inner=2).err() == Null() True - >>> Err(error="Nothing here", traceback="").err() == Some(inner="Nothing here") + >>> Err(error="Nothing here").err() == Some(inner="Nothing here") True :: """ @@ -846,7 +852,7 @@ def expect(self, msg: str) -> Never: def expect_err(self, msg: str) -> E: # noqa: ARG002 """.. code-block:: python - >>> Err(error=2, traceback="").expect_err("must be err") == 2 + >>> Err(error=2).expect_err("must be err") == 2 True :: """ @@ -864,7 +870,7 @@ def flatten(self) -> Result[Never, E]: >>> Ok(inner=2).flatten() == Ok(inner=2) True - >>> Err(error=None, traceback="").flatten() == Err(error=None, traceback="") + >>> Err(error=None).flatten() == Err(error=None) True :: """ @@ -876,9 +882,7 @@ def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - >>> Err(error="un-appendable", traceback="").inspect(append_to_list) == Err( - ... error="un-appendable", traceback="" - ... ) + >>> Err(error="un-appendable").inspect(append_to_list) == Err(error="un-appendable") True :: """ @@ -887,7 +891,7 @@ def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: """.. code-block:: python - >>> Err(error=[1], traceback="").inspect_err(append_to_list) == Err(error=[1], traceback="") + >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") @@ -903,7 +907,7 @@ def is_err(self) -> bool: >>> Ok(inner=2).is_err() == False True - >>> Err(error=2, traceback="").is_err() == True + >>> Err(error=2).is_err() == True True :: """ @@ -912,10 +916,10 @@ def is_err(self) -> bool: def is_err_and(self, fn: Callable[[E], bool]) -> bool: """.. code-block:: python - >>> Err(error=1, traceback="").is_err_and(is_even) == False + >>> Err(error=1).is_err_and(is_even) == False True - >>> Err(error=2, traceback="").is_err_and(is_even) == True + >>> Err(error=2).is_err_and(is_even) == True True >>> Ok(inner=2).is_err_and(is_even) == False @@ -930,7 +934,7 @@ def is_ok(self) -> bool: >>> Ok(inner=2).is_ok() == True True - >>> Err(error=2, traceback="").is_ok() == False + >>> Err(error=2).is_ok() == False True :: """ @@ -945,7 +949,7 @@ def is_ok_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 >>> Ok(inner=2).is_ok_and(is_even) == True True - >>> Err(error=2, traceback="").is_ok_and(is_even) == False + >>> Err(error=2).is_ok_and(is_even) == False True :: """ @@ -962,7 +966,7 @@ def map[U, **P]( >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - >>> Err(error=1, traceback="").map(add_one) == Err(error=1, traceback="") + >>> Err(error=1).map(add_one) == Err(error=1) True :: """ @@ -973,7 +977,7 @@ def map_err[F, **P]( ) -> Result[Never, F]: """.. code-block:: python - >>> Err(error=1, traceback="").map_err(add_one) == Err(error=2, traceback="") + >>> Err(error=1).map_err(add_one) == Err(error=2) True >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) @@ -991,6 +995,15 @@ def map_or[U, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> U: + """.. code-block:: python + + >>> Ok(inner="foo").map_or(42, len) == 3 + True + + >>> Err(error=None).map_or(42, len) == 42 + True + :: + """ return default def map_or_else[U, **P]( @@ -1000,6 +1013,15 @@ def map_or_else[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: + """.. code-block:: python + + >>> Ok(inner="foo").map_or_else(get_42, len) == 3 + True + + >>> Err(error=None).map_or_else(get_42, len) == 42 + True + :: + """ return default(*args, **kwargs) def ok(self) -> Option[Never]: @@ -1008,7 +1030,7 @@ def ok(self) -> Option[Never]: >>> Ok(inner=2).ok() == Some(inner=2) True - >>> Err(error=2, traceback="").ok() == Null() + >>> Err(error=2).ok() == Null() True :: """ @@ -1019,18 +1041,16 @@ def ok(self) -> Option[Never]: def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: """.. code-block:: python - >>> Ok(inner=2).or_(Err(error="foo", traceback="")) == Ok(inner=2) + >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - >>> Err(error="foo", traceback="").or_(Ok(inner=100)) == Ok(inner=100) + >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - >>> Err(error="foo", traceback="").or_(Err(error="foo", traceback="")) == Err( - ... error="foo", traceback="" - ... ) + >>> Err(error="foo").or_(Err(error="foo")) == Err(error="foo") True :: """ @@ -1044,10 +1064,10 @@ def or_else[F, **P]( >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - >>> Err(error="foo", traceback="").or_else(get_ok_vikings) == Ok(inner="vikings") + >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - >>> Err(error="foo", traceback="").or_else(Err) == Err(error="foo", traceback="") + >>> Err(error="foo").or_else(Err) == Err(error="foo") True :: """ @@ -1062,7 +1082,7 @@ def transpose(self) -> Option[Result[Never, E]]: >>> Ok(inner=Null()).transpose() == Null() True - >>> Err(error=None, traceback="").transpose() == Some(inner=Err(error=None, traceback="")) + >>> Err(error=None).transpose() == Some(inner=Err(error=None)) True :: """ @@ -1084,7 +1104,7 @@ def unwrap(self) -> Never: def unwrap_err(self) -> E: """.. code-block:: python - >>> Err(error="failed", traceback="").unwrap_err() == "failed" + >>> Err(error="failed").unwrap_err() == "failed" True :: """ @@ -1096,7 +1116,7 @@ def unwrap_or[U](self, default: U) -> U: >>> Ok(inner="car").unwrap_or("bike") == "car" True - >>> Err(error=None, traceback="").unwrap_or("bike") == "bike" + >>> Err(error=None).unwrap_or("bike") == "bike" True :: """ @@ -1108,7 +1128,7 @@ def unwrap_or_else[U](self, fn: Callable[[E], U]) -> U: >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - >>> Err(error=None, traceback="").unwrap_or_else(get_42) == 42 + >>> Err(error=None).unwrap_or_else(get_42) == 42 True :: """ diff --git a/tests/monads/test_option.py b/tests/monads/test_option.py index 379743e..828beb7 100644 --- a/tests/monads/test_option.py +++ b/tests/monads/test_option.py @@ -161,7 +161,7 @@ def test_map(monad: Option, fn, expected_result) -> None: [pytest.param(Some("foo"), 42, len, 3), pytest.param(Null(), 42, len, 42)], ) def test_map_or(monad: Option, default, fn, expected_result) -> None: - assert monad.map_or(default, fn) == expected_result + assert example(monad.map_or, default, fn) == expected_result @pytest.mark.parametrize( @@ -169,7 +169,7 @@ def test_map_or(monad: Option, default, fn, expected_result) -> None: [pytest.param(Some("foo"), get_42, len, 3), pytest.param(Null(), get_42, len, 42)], ) def test_map_or_else(monad: Option, default, fn, expected_result) -> None: - assert monad.map_or_else(default, fn) == expected_result + assert example(monad.map_or_else, default, fn) == expected_result @pytest.mark.parametrize( diff --git a/tests/monads/test_result_v2.py b/tests/monads/test_result_v2.py index 8398378..4db1914 100644 --- a/tests/monads/test_result_v2.py +++ b/tests/monads/test_result_v2.py @@ -176,7 +176,7 @@ def test_map_err(monad: Result, fn, expected_result) -> None: [pytest.param(Ok("foo"), 42, len, 3), pytest.param(Err(), 42, len, 42)], ) def test_map_or(monad: Result, default, fn, expected_result) -> None: - assert monad.map_or(default, fn) == expected_result + assert example(monad.map_or, default, fn) == expected_result @pytest.mark.parametrize( @@ -184,7 +184,7 @@ def test_map_or(monad: Result, default, fn, expected_result) -> None: [pytest.param(Ok("foo"), get_42, len, 3), pytest.param(Err(), get_42, len, 42)], ) def test_map_or_else(monad: Result, default, fn, expected_result) -> None: - assert monad.map_or_else(default, fn) == expected_result + assert example(monad.map_or_else, default, fn) == expected_result @pytest.mark.parametrize( From 83ca6a6a267a7fc62ea5a5c3efe4a6e3b0c9e1f3 Mon Sep 17 00:00:00 2001 From: ed cuss Date: Fri, 25 Sep 2026 21:24:46 +0100 Subject: [PATCH 06/11] fix: mkdir if not exists --- dev_tools/create_examples/collection/recorder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dev_tools/create_examples/collection/recorder.py b/dev_tools/create_examples/collection/recorder.py index d05af10..133364e 100644 --- a/dev_tools/create_examples/collection/recorder.py +++ b/dev_tools/create_examples/collection/recorder.py @@ -34,6 +34,7 @@ def prepare_files(self) -> Self: def write_examples(self) -> Self: for path, data in self.files.items(): + path.parent.mkdir(parents=True, exist_ok=True) path.write_text(data) return self From 93ca7b27764ec59c331b7dadf3c906fb565c0172 Mon Sep 17 00:00:00 2001 From: ed cuss Date: Fri, 25 Sep 2026 21:51:45 +0100 Subject: [PATCH 07/11] docs: update Either docs --- .../transformation/ast_editing.py | 4 +- src/danom/_monads/_either.py | 173 +++++++++--------- tests/monads/test_either.py | 9 +- 3 files changed, 92 insertions(+), 94 deletions(-) diff --git a/dev_tools/create_examples/transformation/ast_editing.py b/dev_tools/create_examples/transformation/ast_editing.py index 4748d8f..41a2be2 100644 --- a/dev_tools/create_examples/transformation/ast_editing.py +++ b/dev_tools/create_examples/transformation/ast_editing.py @@ -94,6 +94,4 @@ def _format_example(example: str) -> str: if not lines: return "" - return lines[0] + textwrap.indent( - "".join(lines[1:]), " ", predicate=lambda line: bool(line.strip()) - ) + return textwrap.indent("".join(lines), " ", predicate=lambda line: bool(line.strip())) diff --git a/src/danom/_monads/_either.py b/src/danom/_monads/_either.py index e6381c6..82ed7ef 100644 --- a/src/danom/_monads/_either.py +++ b/src/danom/_monads/_either.py @@ -31,21 +31,7 @@ class Either[T_co, E_co: object](ABC): @classmethod def unit(cls, inner: T_co) -> Right[T_co]: - """Unit method. Given an item of type ``T`` return ``Right(T)`` - - .. doctest:: - - >>> from danom import Left, Right, Either - - >>> Either.unit(0) == Right(inner=0) - True - - >>> Right.unit(0) == Right(inner=0) - True - - >>> Left.unit(0) == Right(inner=0) - True - """ + """Unit method. Given an item of type ``T`` return ``Right(T)``""" return Right(inner) @abstractmethod @@ -53,15 +39,15 @@ def is_ok(self) -> bool: """Returns ``True`` if the result type is ``Right``. Returns ``False`` if the result type is ``Left``. - .. doctest:: - >>> from danom import Left, Right + .. code-block:: python - >>> Right().is_ok() == True + >>> Right(inner=None).is_ok() == True True - >>> Left().is_ok() == False + >>> Left(inner=None).is_ok() == False True + :: """ ... @@ -70,12 +56,15 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Either[U_co, """Pipe a pure function and wrap the return value with ``Right``. Given an ``Left`` will return ``self``. + .. code-block:: python - from danom import Left, Right + >>> Right(inner=0).map(add_one) == Right(inner=1) + True - Right(1).map(add_one) == Right(2) - Left(1).map(add_one) == Left(1) + >>> Left(inner=None).map(add_one) == Left(inner=None) + True + :: """ ... @@ -84,106 +73,62 @@ def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Either[U """Pipe a pure function and wrap the return value with ``Left``. Given an ``Right`` will return ``self``. + .. code-block:: python - from danom import Left, Right + >>> Right(inner=0).map_err(add_one) == Right(inner=0) + True - Left(TypeError()).map_err(type_err_to_value_err) == Left(ValueError()) - Right(1).map(type_err_to_value_err) == Right(1) + >>> Left(inner=0).map_err(add_one) == Left(inner=1) + True + :: """ ... @abstractmethod def and_then(self, func: Bindable, *args: P.args, **kwargs: P.kwargs) -> Either[U_co, E_co]: - """Pipe another function that returns a monad. For ``Left`` will return original inner. - - .. code-block:: python - - from danom import Left, Right - - Right(1).and_then(add_one) == Right(2) - Right(1).and_then(raise_err) == Left(TypeError()) - Left(TypeError()).and_then(add_one) == Left(TypeError()) - Left(TypeError()).and_then(raise_value_err) == Left(TypeError()) - """ + """Pipe another function that returns a monad. For ``Left`` will return original inner.""" ... @abstractmethod def or_else(self, func: Bindable, *args: P.args, **kwargs: P.kwargs) -> Either[U_co, E_co]: - """Pipe a function that returns a monad to recover from an ``Left``. For ``Right`` will return original ``Either``. - - .. code-block:: python - - from danom import Left, Right - - Right(1).or_else(replace_err_with_zero) == Right(1) - Left(TypeError()).or_else(replace_err_with_zero) == Right(0) - """ + """Pipe a function that returns a monad to recover from an ``Left``. For ``Right`` will return original ``Either``.""" ... @abstractmethod def unwrap(self) -> T_co: - """Unwrap the `Right` or ``Left`` monad to get the inner value. - - .. doctest:: - - >>> from danom import Left, Right - - >>> Right().unwrap() == None - True - - >>> Right(1).unwrap() == 1 - True - - >>> Right("ok").unwrap() == 'ok' - True - - >>> Left(-1).unwrap() == -1 - True - - """ + """Unwrap the `Right` or ``Left`` monad to get the inner value.""" ... @staticmethod def either_is_ok(result: Either[T_co, E_co]) -> bool: - """Check whether the monad is ok. Allows for ``filter`` or ``partition`` in a ``Stream`` without needing a lambda or custom function. - - .. code-block:: python - - from danom import Stream, Either - - Stream.from_iterable([Right(), Right(), Left()]).filter(Either.either_is_ok).collect() == (Right(), Right()) - - """ + """Check whether the monad is ok. Allows for ``filter`` or ``partition`` in a ``Stream`` without needing a lambda or custom function.""" return result.is_ok() @staticmethod def either_unwrap(result: Either[T_co, E_co]) -> T_co: - """Unwrap the `Right` or ``Left`` monad to get the inner value. - - .. code-block:: python - - from danom import Stream, Either - - oks, errs = Stream.from_iterable([Right(1), Right(2), Left()]).partition(Either.either_is_ok) - oks.map(Either.either_unwrap).collect == (1, 2) - - """ + """Unwrap the `Right` or ``Left`` monad to get the inner value.""" return result.unwrap() def flatten(self) -> Either[T_co, E_co]: """Flatten the monad. Will return the first ``Left`` or the lowest ``Right`` instance. - .. doctest:: - >>> from danom import Left, Right, Stream, Either - >>> Right(Right(Right(1))).flatten() == Right(1) + .. code-block:: python + + >>> Right(inner=Right(inner=None)).flatten() == Right(inner=None) + True + + >>> Right(inner=Left(inner=None)).flatten() == Left(inner=None) True - >>> Right(Right(Left())).flatten() == Left() + >>> Left(inner=Right(inner=None)).flatten() == Right(inner=None) True + >>> Left(inner=Left(inner=None)).flatten() == Left(inner=None) + True + :: """ current = self @@ -196,12 +141,39 @@ def flatten(self) -> Either[T_co, E_co]: @attrs.define(frozen=True, hash=True) class Right(Either[T_co, Never]): def is_ok(self) -> Literal[True]: + """.. code-block:: python + + >>> Right(inner=None).is_ok() == True + True + + >>> Left(inner=None).is_ok() == False + True + :: + """ return True def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Right[U_co]: + """.. code-block:: python + + >>> Right(inner=0).map(add_one) == Right(inner=1) + True + + >>> Left(inner=None).map(add_one) == Left(inner=None) + True + :: + """ return Right(func(self.inner, *args, **kwargs)) def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 + """.. code-block:: python + + >>> Right(inner=0).map_err(add_one) == Right(inner=0) + True + + >>> Left(inner=0).map_err(add_one) == Left(inner=1) + True + :: + """ return self def and_then(self, func: Bindable, *args: P.args, **kwargs: P.kwargs) -> Either[U_co, E_co]: @@ -217,12 +189,39 @@ def unwrap(self) -> T_co: @attrs.define(frozen=True, hash=True) class Left(Either[Never, E_co]): def is_ok(self) -> Literal[False]: + """.. code-block:: python + + >>> Right(inner=None).is_ok() == True + True + + >>> Left(inner=None).is_ok() == False + True + :: + """ return False def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 + """.. code-block:: python + + >>> Right(inner=0).map(add_one) == Right(inner=1) + True + + >>> Left(inner=None).map(add_one) == Left(inner=None) + True + :: + """ return self def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Left[F_co]: + """.. code-block:: python + + >>> Right(inner=0).map_err(add_one) == Right(inner=0) + True + + >>> Left(inner=0).map_err(add_one) == Left(inner=1) + True + :: + """ return Left(func(self.inner, *args, **kwargs)) def and_then(self, func: Bindable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 diff --git a/tests/monads/test_either.py b/tests/monads/test_either.py index ab5b29a..3b9a599 100644 --- a/tests/monads/test_either.py +++ b/tests/monads/test_either.py @@ -1,6 +1,7 @@ import pytest from danom import Either, Left, Right +from dev_tools.create_examples.collection.example import example from tests.conftest import add_one @@ -50,7 +51,7 @@ def test_unwrap(monad, inner): ("monad", "expected_result"), [pytest.param(Right(), True), pytest.param(Left(), False)] ) def test_is_ok(monad, expected_result): - assert monad.is_ok() == expected_result + assert example(monad.is_ok) == expected_result @pytest.mark.parametrize( @@ -58,7 +59,7 @@ def test_is_ok(monad, expected_result): [pytest.param(Right(0), add_one, Right(1)), pytest.param(Left(), add_one, Left())], ) def test_map(monad, func, expected_result): - assert monad.map(func) == expected_result + assert example(monad.map, func) == expected_result @pytest.mark.parametrize( @@ -66,7 +67,7 @@ def test_map(monad, func, expected_result): [pytest.param(Right(0), add_one, Right(0)), pytest.param(Left(0), add_one, Left(1))], ) def test_map_err(monad, func, expected_result): - assert monad.map_err(func) == expected_result + assert example(monad.map_err, func) == expected_result @pytest.mark.parametrize( @@ -94,4 +95,4 @@ def test_staticmethod_result_unwrap(monad, inner): ], ) def test_flatten(monad, expected_result) -> None: - assert monad.flatten() == expected_result + assert example(monad.flatten) == expected_result From 1a8aa64d7ae52b216d7d6720a99197afc87b7d1a Mon Sep 17 00:00:00 2001 From: ed cuss Date: Sat, 26 Sep 2026 19:15:42 +0100 Subject: [PATCH 08/11] feat: use test IDs as doc sentences --- .../create_examples/collection/example.py | 18 +- .../create_examples/collection/record.py | 5 +- .../transformation/format_examples.py | 4 +- src/danom/_monads/_either.py | 100 +- src/danom/_monads/_option.py | 936 ++++++++++++++++-- src/danom/_monads/_result_v2.py | 814 +++++++++++++-- tests/monads/test_result_v2.py | 139 ++- 7 files changed, 1797 insertions(+), 219 deletions(-) diff --git a/dev_tools/create_examples/collection/example.py b/dev_tools/create_examples/collection/example.py index 5daf5c6..b3dce27 100644 --- a/dev_tools/create_examples/collection/example.py +++ b/dev_tools/create_examples/collection/example.py @@ -16,10 +16,18 @@ class Example[T]: kwargs: dict[str, Any] actual_result: T recorder: Recorder + description: str | None = None def __eq__(self, expected: T) -> bool: self.recorder.record_example( - ExampleRecord.new(self.fn, self.args, self.kwargs, self.actual_result, expected) + ExampleRecord.new( + self.fn, + self.args, + self.kwargs, + returned=self.actual_result, + expected=expected, + description=self.description, + ) ) return self.actual_result == expected @@ -36,6 +44,10 @@ def __hash__(self) -> int: return hash(hash_value) -def example(fn: Callable, *args: tuple[Any, ...], **kwargs: dict[str, Any]) -> Example: +def example( + fn: Callable, *args: tuple[Any, ...], description: str | None = None, **kwargs: dict[str, Any] +) -> Example: value = fn(*args, **kwargs) - return Example(fn, args, kwargs, value, recorder=_RECORDER) + return Example( + fn, args, kwargs, actual_result=value, description=description, recorder=_RECORDER + ) diff --git a/dev_tools/create_examples/collection/record.py b/dev_tools/create_examples/collection/record.py index 5add869..cc26fd5 100644 --- a/dev_tools/create_examples/collection/record.py +++ b/dev_tools/create_examples/collection/record.py @@ -19,15 +19,17 @@ class ExampleRecord: kwargs: dict[str, Any] returned: Any expected: Any + description: str | None = None @classmethod - def new( + def new( # noqa: PLR0913 cls, fn: Callable, args: tuple[Any, ...], kwargs: dict[str, Any], returned: Any, # noqa: ANN401 expected: Any, # noqa: ANN401 + description: str | None = None, ) -> Self: return cls( cls_name=fn.__self__.__class__.__name__, @@ -41,6 +43,7 @@ def new( kwargs={k: _get_repr(v) for k, v in kwargs.items()}, returned=_get_repr(returned), expected=_get_repr(expected), + description=description, ) def to_dict(self) -> dict[str, str]: diff --git a/dev_tools/create_examples/transformation/format_examples.py b/dev_tools/create_examples/transformation/format_examples.py index 33f2243..cbe1737 100644 --- a/dev_tools/create_examples/transformation/format_examples.py +++ b/dev_tools/create_examples/transformation/format_examples.py @@ -38,13 +38,13 @@ def example_to_str(example: ExampleRecord) -> str: doctest = "\n".join( f"{' >>>' if i == 0 else ' ...'} {line}" for i, line in enumerate(lines) ) - return f"{doctest}\n True" + return f"{example.description or ''}\n\n.. code-block:: python\n\n{doctest}\n True" def reduce_examples_to_example_str( fn_examples: dict[str, dict[str, list[str]]], ) -> dict[str, dict[str, str]]: return { - path: {k: ".. code-block:: python\n\n" + "\n\n".join(v) + "\n::" for k, v in fn.items()} + path: {k: "Papertrail examples:\n\n" + "\n\n".join(v) + "\n::" for k, v in fn.items()} for path, fn in fn_examples.items() } diff --git a/src/danom/_monads/_either.py b/src/danom/_monads/_either.py index 82ed7ef..a281ac2 100644 --- a/src/danom/_monads/_either.py +++ b/src/danom/_monads/_either.py @@ -40,11 +40,19 @@ def is_ok(self) -> bool: Returns ``False`` if the result type is ``Left``. + Papertrail examples: + + + .. code-block:: python >>> Right(inner=None).is_ok() == True True + + + .. code-block:: python + >>> Left(inner=None).is_ok() == False True :: @@ -57,11 +65,19 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Either[U_co, Given an ``Left`` will return ``self``. + Papertrail examples: + + + .. code-block:: python >>> Right(inner=0).map(add_one) == Right(inner=1) True + + + .. code-block:: python + >>> Left(inner=None).map(add_one) == Left(inner=None) True :: @@ -74,11 +90,19 @@ def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Either[U Given an ``Right`` will return ``self``. + Papertrail examples: + + + .. code-block:: python >>> Right(inner=0).map_err(add_one) == Right(inner=0) True + + + .. code-block:: python + >>> Left(inner=0).map_err(add_one) == Left(inner=1) True :: @@ -115,17 +139,33 @@ def flatten(self) -> Either[T_co, E_co]: + Papertrail examples: + + + .. code-block:: python >>> Right(inner=Right(inner=None)).flatten() == Right(inner=None) True + + + .. code-block:: python + >>> Right(inner=Left(inner=None)).flatten() == Left(inner=None) True + + + .. code-block:: python + >>> Left(inner=Right(inner=None)).flatten() == Right(inner=None) True + + + .. code-block:: python + >>> Left(inner=Left(inner=None)).flatten() == Left(inner=None) True :: @@ -141,11 +181,19 @@ def flatten(self) -> Either[T_co, E_co]: @attrs.define(frozen=True, hash=True) class Right(Either[T_co, Never]): def is_ok(self) -> Literal[True]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Right(inner=None).is_ok() == True True + + + .. code-block:: python + >>> Left(inner=None).is_ok() == False True :: @@ -153,11 +201,19 @@ def is_ok(self) -> Literal[True]: return True def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Right[U_co]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Right(inner=0).map(add_one) == Right(inner=1) True + + + .. code-block:: python + >>> Left(inner=None).map(add_one) == Left(inner=None) True :: @@ -165,11 +221,19 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Right[U_co]: return Right(func(self.inner, *args, **kwargs)) def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Right(inner=0).map_err(add_one) == Right(inner=0) True + + + .. code-block:: python + >>> Left(inner=0).map_err(add_one) == Left(inner=1) True :: @@ -189,11 +253,19 @@ def unwrap(self) -> T_co: @attrs.define(frozen=True, hash=True) class Left(Either[Never, E_co]): def is_ok(self) -> Literal[False]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Right(inner=None).is_ok() == True True + + + .. code-block:: python + >>> Left(inner=None).is_ok() == False True :: @@ -201,11 +273,19 @@ def is_ok(self) -> Literal[False]: return False def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Right(inner=0).map(add_one) == Right(inner=1) True + + + .. code-block:: python + >>> Left(inner=None).map(add_one) == Left(inner=None) True :: @@ -213,11 +293,19 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noq return self def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Left[F_co]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Right(inner=0).map_err(add_one) == Right(inner=0) True + + + .. code-block:: python + >>> Left(inner=0).map_err(add_one) == Left(inner=1) True :: diff --git a/src/danom/_monads/_option.py b/src/danom/_monads/_option.py index f86d033..a9bc62e 100644 --- a/src/danom/_monads/_option.py +++ b/src/danom/_monads/_option.py @@ -15,17 +15,33 @@ class Option[T](ABC): @abstractmethod def and_(self, opt_b: Option[T]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).and_(Null()) == Null() True + + + .. code-block:: python + >>> Null().and_(Some(inner="foo")) == Null() True + + + .. code-block:: python + >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True + + + .. code-block:: python + >>> Null().and_(Null()) == Null() True :: @@ -34,14 +50,26 @@ def and_(self, opt_b: Option[T]) -> Option[T]: @abstractmethod def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) True + + + .. code-block:: python + >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True + + + .. code-block:: python + >>> Null().and_then(must_be_less_than_10) == Null() True :: @@ -50,11 +78,19 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: @abstractmethod def as_list(self) -> list[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).as_list() == [2] True + + + .. code-block:: python + >>> Null().as_list() == [] True :: @@ -63,11 +99,19 @@ def as_list(self) -> list[T]: @abstractmethod def as_tuple(self) -> tuple[T, ...]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).as_tuple() == (2,) True + + + .. code-block:: python + >>> Null().as_tuple() == () True :: @@ -75,11 +119,19 @@ def as_tuple(self) -> tuple[T, ...]: ... def cloned(self) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).cloned() == Some(inner=2) True + + + .. code-block:: python + >>> Null().cloned() == Null() True :: @@ -88,7 +140,11 @@ def cloned(self) -> Option[T]: @abstractmethod def expect(self, msg: str) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).expect("must be positive") == 2 True @@ -98,14 +154,26 @@ def expect(self, msg: str) -> T: @abstractmethod def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=3).filter_(is_even) == Null() True + + + .. code-block:: python + >>> Some(inner=4).filter_(is_even) == Some(inner=4) True + + + .. code-block:: python + >>> Null().filter_(is_even) == Null() True :: @@ -114,17 +182,33 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: @abstractmethod def flatten(self) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True + + + .. code-block:: python + >>> Some(inner=2).flatten() == Some(inner=2) True + + + .. code-block:: python + >>> Null().flatten() == Null() True :: @@ -133,11 +217,19 @@ def flatten(self) -> Option[T]: @abstractmethod def inspect(self, fn: Callable[[T], None]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) True + + + .. code-block:: python + >>> Null().inspect(append_to_list) == Null() True :: @@ -146,11 +238,19 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: @abstractmethod def is_none(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).is_none() == False True + + + .. code-block:: python + >>> Null().is_none() == True True :: @@ -159,14 +259,26 @@ def is_none(self) -> bool: @abstractmethod def is_none_or(self, fn: Callable[[T], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).is_none_or(is_even) == False True + + + .. code-block:: python + >>> Some(inner=2).is_none_or(is_even) == True True + + + .. code-block:: python + >>> Null().is_none_or(is_even) == True True :: @@ -175,11 +287,19 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: @abstractmethod def is_some(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).is_some() == True True + + + .. code-block:: python + >>> Null().is_some() == False True :: @@ -188,14 +308,26 @@ def is_some(self) -> bool: @abstractmethod def is_some_and(self, fn: Callable[[T], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).is_some_and(is_even) == False True + + + .. code-block:: python + >>> Some(inner=2).is_some_and(is_even) == True True + + + .. code-block:: python + >>> Null().is_some_and(is_even) == False True :: @@ -204,11 +336,19 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: @abstractmethod def map[U](self, fn: Callable[[T], U]) -> Option[U]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).map(add_one) == Some(inner=2) True + + + .. code-block:: python + >>> Null().map(add_one) == Null() True :: @@ -217,11 +357,19 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: @abstractmethod def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").map_or(42, len) == 3 True + + + .. code-block:: python + >>> Null().map_or(42, len) == 42 True :: @@ -230,11 +378,19 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: @abstractmethod def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").map_or_else(get_42, len) == 3 True + + + .. code-block:: python + >>> Null().map_or_else(get_42, len) == 42 True :: @@ -243,11 +399,19 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: @abstractmethod def ok_or[E](self, err: E) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True + + + .. code-block:: python + >>> Null().ok_or(0) == Err(error=0) True :: @@ -256,11 +420,19 @@ def ok_or[E](self, err: E) -> Result[T, E]: @abstractmethod def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True + + + .. code-block:: python + >>> Null().ok_or_else(get_42) == Err(error=42) True :: @@ -269,17 +441,33 @@ def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: @abstractmethod def or_(self, opt_b: Option[T]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).or_(Null()) == Some(inner=2) True + + + .. code-block:: python + >>> Null().or_(Some(inner=100)) == Some(inner=100) True + + + .. code-block:: python + >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True + + + .. code-block:: python + >>> Null().or_(Null()) == Null() True :: @@ -288,14 +476,26 @@ def or_(self, opt_b: Option[T]) -> Option[T]: @abstractmethod def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") True + + + .. code-block:: python + >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True + + + .. code-block:: python + >>> Null().or_else(Null) == Null() True :: @@ -304,11 +504,19 @@ def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: @abstractmethod def replace(self, value: T) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).replace(5) == Some(inner=5) True + + + .. code-block:: python + >>> Null().replace(3) == Null() True :: @@ -317,14 +525,26 @@ def replace(self, value: T) -> Option[T]: @abstractmethod def transpose[E](self) -> Result[Option[T], E]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True + + + .. code-block:: python + >>> Null().transpose() == Ok(inner=Null()) True :: @@ -333,7 +553,11 @@ def transpose[E](self) -> Result[Option[T], E]: @abstractmethod def unwrap(self) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).unwrap() == 2 True @@ -343,11 +567,19 @@ def unwrap(self) -> T: @abstractmethod def unwrap_or(self, default: T) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="car").unwrap_or("bike") == "car" True + + + .. code-block:: python + >>> Null().unwrap_or("bike") == "bike" True :: @@ -356,11 +588,19 @@ def unwrap_or(self, default: T) -> T: @abstractmethod def unwrap_or_else(self, fn: Callable[..., T]) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=4).unwrap_or_else(get_42) == 4 True + + + .. code-block:: python + >>> Null().unwrap_or_else(get_42) == 42 True :: @@ -369,14 +609,26 @@ def unwrap_or_else(self, fn: Callable[..., T]) -> T: @abstractmethod def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) True + + + .. code-block:: python + >>> Some(inner=1).zip(Null()) == Null() True + + + .. code-block:: python + >>> Null().zip(Some(inner=1)) == Null() True :: @@ -385,14 +637,26 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: @abstractmethod def unzip[U](self) -> tuple[Option[T], Option[U]]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=4).unzip() == (Null(), Null()) True + + + .. code-block:: python + >>> Null().unzip() == (Null(), Null()) True :: @@ -405,17 +669,33 @@ class Some[T](Option): inner: T def and_(self, opt_b: Option[T]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).and_(Null()) == Null() True + + + .. code-block:: python + >>> Null().and_(Some(inner="foo")) == Null() True + + + .. code-block:: python + >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True + + + .. code-block:: python + >>> Null().and_(Null()) == Null() True :: @@ -423,14 +703,26 @@ def and_(self, opt_b: Option[T]) -> Option[T]: return opt_b def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) True + + + .. code-block:: python + >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True + + + .. code-block:: python + >>> Null().and_then(must_be_less_than_10) == Null() True :: @@ -438,11 +730,19 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: return fn(self.inner) def as_list(self) -> list[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).as_list() == [2] True + + + .. code-block:: python + >>> Null().as_list() == [] True :: @@ -450,11 +750,19 @@ def as_list(self) -> list[T]: return [self.inner] def as_tuple(self) -> tuple[T, ...]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).as_tuple() == (2,) True + + + .. code-block:: python + >>> Null().as_tuple() == () True :: @@ -462,7 +770,11 @@ def as_tuple(self) -> tuple[T, ...]: return (self.inner,) def expect(self, msg: str) -> T: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).expect("must be positive") == 2 True @@ -471,14 +783,26 @@ def expect(self, msg: str) -> T: # noqa: ARG002 return self.inner def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=3).filter_(is_even) == Null() True + + + .. code-block:: python + >>> Some(inner=4).filter_(is_even) == Some(inner=4) True + + + .. code-block:: python + >>> Null().filter_(is_even) == Null() True :: @@ -486,17 +810,33 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: return self if predicate(self.inner) else Null() def flatten(self) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True + + + .. code-block:: python + >>> Some(inner=2).flatten() == Some(inner=2) True + + + .. code-block:: python + >>> Null().flatten() == Null() True :: @@ -506,11 +846,19 @@ def flatten(self) -> Option[T]: return self def inspect(self, fn: Callable[[T], None]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: - >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) + + + .. code-block:: python + + >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) True + + + .. code-block:: python + >>> Null().inspect(append_to_list) == Null() True :: @@ -519,11 +867,19 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: return self def is_none(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).is_none() == False True + + + .. code-block:: python + >>> Null().is_none() == True True :: @@ -531,14 +887,26 @@ def is_none(self) -> bool: return False def is_none_or(self, fn: Callable[[T], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).is_none_or(is_even) == False True + + + .. code-block:: python + >>> Some(inner=2).is_none_or(is_even) == True True + + + .. code-block:: python + >>> Null().is_none_or(is_even) == True True :: @@ -546,11 +914,19 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: return fn(self.inner) def is_some(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).is_some() == True True + + + .. code-block:: python + >>> Null().is_some() == False True :: @@ -558,14 +934,26 @@ def is_some(self) -> bool: return True def is_some_and(self, fn: Callable[[T], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).is_some_and(is_even) == False True + + + .. code-block:: python + >>> Some(inner=2).is_some_and(is_even) == True True + + + .. code-block:: python + >>> Null().is_some_and(is_even) == False True :: @@ -573,11 +961,19 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: return fn(self.inner) def map[U](self, fn: Callable[[T], U]) -> Option[U]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).map(add_one) == Some(inner=2) True + + + .. code-block:: python + >>> Null().map(add_one) == Null() True :: @@ -585,11 +981,19 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: return Some(fn(self.inner)) def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").map_or(42, len) == 3 True + + + .. code-block:: python + >>> Null().map_or(42, len) == 42 True :: @@ -597,11 +1001,19 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 return fn(self.inner) def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").map_or_else(get_42, len) == 3 True + + + .. code-block:: python + >>> Null().map_or_else(get_42, len) == 42 True :: @@ -609,11 +1021,19 @@ def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: return fn(self.inner) def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True + + + .. code-block:: python + >>> Null().ok_or(0) == Err(error=0) True :: @@ -623,11 +1043,19 @@ def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 return cast(Result[T, E], Ok(self.inner)) def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True + + + .. code-block:: python + >>> Null().ok_or_else(get_42) == Err(error=42) True :: @@ -637,17 +1065,33 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 return cast(Result[T, E], Ok(self.inner)) def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).or_(Null()) == Some(inner=2) True + + + .. code-block:: python + >>> Null().or_(Some(inner=100)) == Some(inner=100) True + + + .. code-block:: python + >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True + + + .. code-block:: python + >>> Null().or_(Null()) == Null() True :: @@ -655,14 +1099,26 @@ def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 return self def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") True + + + .. code-block:: python + >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True + + + .. code-block:: python + >>> Null().or_else(Null) == Null() True :: @@ -670,11 +1126,19 @@ def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: # noqa: ARG002 return self def replace(self, value: T) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).replace(5) == Some(inner=5) True + + + .. code-block:: python + >>> Null().replace(3) == Null() True :: @@ -682,14 +1146,26 @@ def replace(self, value: T) -> Option[T]: return Some(value) def transpose(self) -> Result[Option[T], Option[T]]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True + + + .. code-block:: python + >>> Null().transpose() == Ok(inner=Null()) True :: @@ -703,7 +1179,11 @@ def transpose(self) -> Result[Option[T], Option[T]]: raise TypeError("inner must be a `Result` type") def unwrap(self) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).unwrap() == 2 True @@ -712,11 +1192,19 @@ def unwrap(self) -> T: return self.inner def unwrap_or(self, default: T) -> T: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="car").unwrap_or("bike") == "car" True + + + .. code-block:: python + >>> Null().unwrap_or("bike") == "bike" True :: @@ -724,11 +1212,19 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 return self.inner def unwrap_or_else(self, fn: Callable[[], T]) -> T: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=4).unwrap_or_else(get_42) == 4 True + + + .. code-block:: python + >>> Null().unwrap_or_else(get_42) == 42 True :: @@ -736,14 +1232,26 @@ def unwrap_or_else(self, fn: Callable[[], T]) -> T: # noqa: ARG002 return self.inner def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) True + + + .. code-block:: python + >>> Some(inner=1).zip(Null()) == Null() True + + + .. code-block:: python + >>> Null().zip(Some(inner=1)) == Null() True :: @@ -753,14 +1261,26 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: return Null[tuple[T, U]]() def unzip[U](self) -> tuple[Option[T], Option[U]]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=4).unzip() == (Null(), Null()) True + + + .. code-block:: python + >>> Null().unzip() == (Null(), Null()) True :: @@ -773,17 +1293,33 @@ def unzip[U](self) -> tuple[Option[T], Option[U]]: @attrs.define(frozen=True) class Null[T](Option): def and_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).and_(Null()) == Null() True + + + .. code-block:: python + >>> Null().and_(Some(inner="foo")) == Null() True + + + .. code-block:: python + >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True + + + .. code-block:: python + >>> Null().and_(Null()) == Null() True :: @@ -791,14 +1327,26 @@ def and_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 return self def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) True + + + .. code-block:: python + >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True + + + .. code-block:: python + >>> Null().and_then(must_be_less_than_10) == Null() True :: @@ -806,11 +1354,19 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: # noqa: ARG00 return self def as_list(self) -> list[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).as_list() == [2] True + + + .. code-block:: python + >>> Null().as_list() == [] True :: @@ -818,11 +1374,19 @@ def as_list(self) -> list[T]: return [] def as_tuple(self) -> tuple[T, ...]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).as_tuple() == (2,) True + + + .. code-block:: python + >>> Null().as_tuple() == () True :: @@ -830,7 +1394,11 @@ def as_tuple(self) -> tuple[T, ...]: return () def expect(self, msg: str) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).expect("must be positive") == 2 True @@ -839,14 +1407,26 @@ def expect(self, msg: str) -> T: raise ValueError(msg) def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=3).filter_(is_even) == Null() True + + + .. code-block:: python + >>> Some(inner=4).filter_(is_even) == Some(inner=4) True + + + .. code-block:: python + >>> Null().filter_(is_even) == Null() True :: @@ -854,17 +1434,33 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: # noqa: ARG002 return self def flatten(self) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True + + + .. code-block:: python + >>> Some(inner=2).flatten() == Some(inner=2) True + + + .. code-block:: python + >>> Null().flatten() == Null() True :: @@ -872,11 +1468,19 @@ def flatten(self) -> Option[T]: return self def inspect(self, fn: Callable[[T], None]) -> Option[T]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) True + + + .. code-block:: python + >>> Null().inspect(append_to_list) == Null() True :: @@ -884,11 +1488,19 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: # noqa: ARG002 return self def is_none(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).is_none() == False True + + + .. code-block:: python + >>> Null().is_none() == True True :: @@ -896,14 +1508,26 @@ def is_none(self) -> bool: return True def is_none_or(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).is_none_or(is_even) == False True + + + .. code-block:: python + >>> Some(inner=2).is_none_or(is_even) == True True + + + .. code-block:: python + >>> Null().is_none_or(is_even) == True True :: @@ -911,11 +1535,19 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 return True def is_some(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).is_some() == True True + + + .. code-block:: python + >>> Null().is_some() == False True :: @@ -923,14 +1555,26 @@ def is_some(self) -> bool: return False def is_some_and(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).is_some_and(is_even) == False True + + + .. code-block:: python + >>> Some(inner=2).is_some_and(is_even) == True True + + + .. code-block:: python + >>> Null().is_some_and(is_even) == False True :: @@ -938,11 +1582,19 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 return False def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).map(add_one) == Some(inner=2) True + + + .. code-block:: python + >>> Null().map(add_one) == Null() True :: @@ -950,11 +1602,19 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 return self def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").map_or(42, len) == 3 True + + + .. code-block:: python + >>> Null().map_or(42, len) == 42 True :: @@ -962,11 +1622,19 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 return default def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").map_or_else(get_42, len) == 3 True + + + .. code-block:: python + >>> Null().map_or_else(get_42, len) == 42 True :: @@ -974,11 +1642,19 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: return default() def ok_or[E](self, err: E) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True + + + .. code-block:: python + >>> Null().ok_or(0) == Err(error=0) True :: @@ -988,11 +1664,19 @@ def ok_or[E](self, err: E) -> Result[T, E]: return cast(Result[T, E], Err[E](err)) def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True + + + .. code-block:: python + >>> Null().ok_or_else(get_42) == Err(error=42) True :: @@ -1002,17 +1686,33 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: return cast(Result[T, E], Err[E](err())) def or_(self, opt_b: Option[T]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).or_(Null()) == Some(inner=2) True + + + .. code-block:: python + >>> Null().or_(Some(inner=100)) == Some(inner=100) True + + + .. code-block:: python + >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True + + + .. code-block:: python + >>> Null().or_(Null()) == Null() True :: @@ -1020,14 +1720,26 @@ def or_(self, opt_b: Option[T]) -> Option[T]: return opt_b def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") True + + + .. code-block:: python + >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True + + + .. code-block:: python + >>> Null().or_else(Null) == Null() True :: @@ -1035,11 +1747,19 @@ def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: return opt_b() def replace(self, value: T) -> Option[T]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).replace(5) == Some(inner=5) True + + + .. code-block:: python + >>> Null().replace(3) == Null() True :: @@ -1047,14 +1767,26 @@ def replace(self, value: T) -> Option[T]: # noqa: ARG002 return self def transpose[E](self) -> Result[Option[T], E]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True + + + .. code-block:: python + >>> Null().transpose() == Ok(inner=Null()) True :: @@ -1064,7 +1796,11 @@ def transpose[E](self) -> Result[Option[T], E]: return cast(Result[Option[T], E], Ok(self)) def unwrap(self) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=2).unwrap() == 2 True @@ -1073,11 +1809,19 @@ def unwrap(self) -> T: raise TypeError("Can't call `unwrap` on `Null`") def unwrap_or(self, default: T) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner="car").unwrap_or("bike") == "car" True + + + .. code-block:: python + >>> Null().unwrap_or("bike") == "bike" True :: @@ -1085,11 +1829,19 @@ def unwrap_or(self, default: T) -> T: return default def unwrap_or_else(self, fn: Callable[[], T]) -> T: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=4).unwrap_or_else(get_42) == 4 True + + + .. code-block:: python + >>> Null().unwrap_or_else(get_42) == 42 True :: @@ -1097,14 +1849,26 @@ def unwrap_or_else(self, fn: Callable[[], T]) -> T: return fn() def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) True + + + .. code-block:: python + >>> Some(inner=1).zip(Null()) == Null() True + + + .. code-block:: python + >>> Null().zip(Some(inner=1)) == Null() True :: @@ -1112,14 +1876,26 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: # noqa: ARG002 return self def unzip[U](self) -> tuple[Option[T], Option[U]]: - """.. code-block:: python + """Papertrail examples: + + + + .. code-block:: python >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) True + + + .. code-block:: python + >>> Some(inner=4).unzip() == (Null(), Null()) True + + + .. code-block:: python + >>> Null().unzip() == (Null(), Null()) True :: diff --git a/src/danom/_monads/_result_v2.py b/src/danom/_monads/_result_v2.py index 9782b88..6d6d66a 100644 --- a/src/danom/_monads/_result_v2.py +++ b/src/danom/_monads/_result_v2.py @@ -63,17 +63,33 @@ def result_unwrap(result: Result[T, E]) -> T: @abstractmethod def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: - """.. code-block:: python + """Papertrail examples: + + Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. + + .. code-block:: python >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True + Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. + + .. code-block:: python + >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True + Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. + + .. code-block:: python + >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True + Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned. + + .. code-block:: python + >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") True :: @@ -84,14 +100,26 @@ def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: def and_then[U, F, **P]( self, fn: Callable[Concatenate[T, P], Result[U, F]], *args: P.args, **kwargs: P.kwargs ) -> Result[U, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-must_be_less_than_10-expected_result0 + + .. code-block:: python >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True + monad1-must_be_less_than_10-expected_result1 + + .. code-block:: python + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True + monad2-must_be_less_than_10-expected_result2 + + .. code-block:: python + >>> Err(error="not a number").and_then(must_be_less_than_10) == Err(error="not a number") True :: @@ -99,11 +127,19 @@ def and_then[U, F, **P]( ... def cloned(self) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).cloned() == Ok(inner=2) True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error=2).cloned() == Err(error=2) True :: @@ -112,11 +148,19 @@ def cloned(self) -> Result[T, E]: @abstractmethod def err(self) -> Option[E]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).err() == Null() True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error="Nothing here").err() == Some(inner="Nothing here") True :: @@ -125,7 +169,11 @@ def err(self) -> Option[E]: @abstractmethod def expect(self, msg: str) -> T: - """.. code-block:: python + """Papertrail examples: + + monad0-must be positive-2-expected_context0 + + .. code-block:: python >>> Ok(inner=2).expect("must be positive") == 2 True @@ -135,7 +183,11 @@ def expect(self, msg: str) -> T: @abstractmethod def expect_err(self, msg: str) -> E: - """.. code-block:: python + """Papertrail examples: + + monad0-must be err-2-expected_context0 + + .. code-block:: python >>> Err(error=2).expect_err("must be err") == 2 True @@ -145,17 +197,33 @@ def expect_err(self, msg: str) -> E: @abstractmethod def flatten(self) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True + monad1-expected_result1 + + .. code-block:: python + >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True + monad2-expected_result2 + + .. code-block:: python + >>> Ok(inner=2).flatten() == Ok(inner=2) True + monad3-expected_result3 + + .. code-block:: python + >>> Err(error=None).flatten() == Err(error=None) True :: @@ -164,11 +232,19 @@ def flatten(self) -> Result[T, E]: @abstractmethod def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-append_to_list-expected_result0 + + .. code-block:: python >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True + monad1-append_to_list-expected_result1 + + .. code-block:: python + >>> Err(error="un-appendable").inspect(append_to_list) == Err(error="un-appendable") True :: @@ -177,11 +253,19 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: @abstractmethod def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-append_to_list-expected_result0 + + .. code-block:: python >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True + monad1-append_to_list-expected_result1 + + .. code-block:: python + >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") True :: @@ -190,11 +274,19 @@ def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: @abstractmethod def is_err(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-False + + .. code-block:: python >>> Ok(inner=2).is_err() == False True + monad1-True + + .. code-block:: python + >>> Err(error=2).is_err() == True True :: @@ -203,14 +295,26 @@ def is_err(self) -> bool: @abstractmethod def is_err_and(self, fn: Callable[[E], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-is_even-False + + .. code-block:: python >>> Err(error=1).is_err_and(is_even) == False True + monad1-is_even-True + + .. code-block:: python + >>> Err(error=2).is_err_and(is_even) == True True + monad2-is_even-False + + .. code-block:: python + >>> Ok(inner=2).is_err_and(is_even) == False True :: @@ -219,11 +323,19 @@ def is_err_and(self, fn: Callable[[E], bool]) -> bool: @abstractmethod def is_ok(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-True + + .. code-block:: python >>> Ok(inner=2).is_ok() == True True + monad1-False + + .. code-block:: python + >>> Err(error=2).is_ok() == False True :: @@ -232,14 +344,26 @@ def is_ok(self) -> bool: @abstractmethod def is_ok_and(self, fn: Callable[[T], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-is_even-False + + .. code-block:: python >>> Ok(inner=1).is_ok_and(is_even) == False True + monad1-is_even-True + + .. code-block:: python + >>> Ok(inner=2).is_ok_and(is_even) == True True + monad2-is_even-False + + .. code-block:: python + >>> Err(error=2).is_ok_and(is_even) == False True :: @@ -250,11 +374,19 @@ def is_ok_and(self, fn: Callable[[T], bool]) -> bool: def map[U, **P]( self, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs ) -> Result[U, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-add_one-expected_result0 + + .. code-block:: python >>> Ok(inner=1).map(add_one) == Ok(inner=2) True + monad1-add_one-expected_result1 + + .. code-block:: python + >>> Err(error=1).map(add_one) == Err(error=1) True :: @@ -265,11 +397,19 @@ def map[U, **P]( def map_err[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs ) -> Result[T, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-add_one-expected_result0 + + .. code-block:: python >>> Err(error=1).map_err(add_one) == Err(error=2) True + monad1-add_one-expected_result1 + + .. code-block:: python + >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) True :: @@ -280,11 +420,19 @@ def map_err[F, **P]( def map_or[U, **P]( self, default: U, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs ) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-42-len-3 + + .. code-block:: python >>> Ok(inner="foo").map_or(42, len) == 3 True + monad1-42-len-42 + + .. code-block:: python + >>> Err(error=None).map_or(42, len) == 42 True :: @@ -299,11 +447,19 @@ def map_or_else[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-get_42-len-3 + + .. code-block:: python >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True + monad1-get_42-len-42 + + .. code-block:: python + >>> Err(error=None).map_or_else(get_42, len) == 42 True :: @@ -312,11 +468,19 @@ def map_or_else[U, **P]( @abstractmethod def ok(self) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).ok() == Some(inner=2) True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error=2).ok() == Null() True :: @@ -325,17 +489,33 @@ def ok(self) -> Option[T]: @abstractmethod def or_[F](self, res: Result[T, F]) -> Result[T, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-res0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True + monad1-res1-expected_result1 + + .. code-block:: python + >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True + monad2-res2-expected_result2 + + .. code-block:: python + >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True + monad3-res3-expected_result3 + + .. code-block:: python + >>> Err(error="foo").or_(Err(error="foo")) == Err(error="foo") True :: @@ -346,14 +526,26 @@ def or_[F](self, res: Result[T, F]) -> Result[T, F]: def or_else[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs ) -> Result[T, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-get_ok_vikings-expected_result0 + + .. code-block:: python >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True + monad1-get_ok_vikings-expected_result1 + + .. code-block:: python + >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True + monad2-Err-expected_result2 + + .. code-block:: python + >>> Err(error="foo").or_else(Err) == Err(error="foo") True :: @@ -362,14 +554,26 @@ def or_else[F, **P]( @abstractmethod def transpose(self) -> Option[Result[T, E]]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0-expected_context0 + + .. code-block:: python >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True + monad1-expected_result1-expected_context1 + + .. code-block:: python + >>> Ok(inner=Null()).transpose() == Null() True + monad2-expected_result2-expected_context2 + + .. code-block:: python + >>> Err(error=None).transpose() == Some(inner=Err(error=None)) True :: @@ -378,7 +582,11 @@ def transpose(self) -> Option[Result[T, E]]: @abstractmethod def unwrap(self) -> T: - """.. code-block:: python + """Papertrail examples: + + monad0-2-expected_context0 + + .. code-block:: python >>> Ok(inner=2).unwrap() == 2 True @@ -388,7 +596,11 @@ def unwrap(self) -> T: @abstractmethod def unwrap_err(self) -> E: - """.. code-block:: python + """Papertrail examples: + + monad0-failed-expected_context0 + + .. code-block:: python >>> Err(error="failed").unwrap_err() == "failed" True @@ -398,11 +610,19 @@ def unwrap_err(self) -> E: @abstractmethod def unwrap_or(self, default: T) -> T: - """.. code-block:: python + """Papertrail examples: + + monad0-bike-car + + .. code-block:: python >>> Ok(inner="car").unwrap_or("bike") == "car" True + monad1-bike-bike + + .. code-block:: python + >>> Err(error=None).unwrap_or("bike") == "bike" True :: @@ -411,11 +631,19 @@ def unwrap_or(self, default: T) -> T: @abstractmethod def unwrap_or_else(self, fn: Callable[[E], T]) -> T: - """.. code-block:: python + """Papertrail examples: + + monad0-get_42-4 + + .. code-block:: python >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True + monad1-get_42-42 + + .. code-block:: python + >>> Err(error=None).unwrap_or_else(get_42) == 42 True :: @@ -428,17 +656,33 @@ class Ok[T](Result[T, Never]): inner: T = attrs.field(default=None) def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: - """.. code-block:: python + """Papertrail examples: + + Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. + + .. code-block:: python >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True + Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. + + .. code-block:: python + >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True + Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. + + .. code-block:: python + >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True + Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned. + + .. code-block:: python + >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") True :: @@ -448,14 +692,26 @@ def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: def and_then[U, E, **P]( self, fn: Callable[Concatenate[T, P], Result[U, E]], *args: P.args, **kwargs: P.kwargs ) -> Result[U, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-must_be_less_than_10-expected_result0 + + .. code-block:: python >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True + monad1-must_be_less_than_10-expected_result1 + + .. code-block:: python + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True + monad2-must_be_less_than_10-expected_result2 + + .. code-block:: python + >>> Err(error="not a number").and_then(must_be_less_than_10) == Err(error="not a number") True :: @@ -463,11 +719,19 @@ def and_then[U, E, **P]( return fn(self.inner, *args, **kwargs) def err[E](self) -> Option[E]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).err() == Null() True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error="Nothing here").err() == Some(inner="Nothing here") True :: @@ -477,7 +741,11 @@ def err[E](self) -> Option[E]: return Null() def expect(self, msg: str) -> T: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-must be positive-2-expected_context0 + + .. code-block:: python >>> Ok(inner=2).expect("must be positive") == 2 True @@ -486,7 +754,11 @@ def expect(self, msg: str) -> T: # noqa: ARG002 return self.inner def expect_err(self, msg: str) -> Never: - """.. code-block:: python + """Papertrail examples: + + monad0-must be err-2-expected_context0 + + .. code-block:: python >>> Err(error=2).expect_err("must be err") == 2 True @@ -495,17 +767,33 @@ def expect_err(self, msg: str) -> Never: raise ValueError(msg) def flatten(self) -> Result[T, Never]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True + monad1-expected_result1 + + .. code-block:: python + >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True + monad2-expected_result2 + + .. code-block:: python + >>> Ok(inner=2).flatten() == Ok(inner=2) True + monad3-expected_result3 + + .. code-block:: python + >>> Err(error=None).flatten() == Err(error=None) True :: @@ -515,11 +803,19 @@ def flatten(self) -> Result[T, Never]: return cast(Result[T, Never], self) def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: - """.. code-block:: python + """Papertrail examples: + + monad0-append_to_list-expected_result0 + + .. code-block:: python >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True + monad1-append_to_list-expected_result1 + + .. code-block:: python + >>> Err(error="un-appendable").inspect(append_to_list) == Err(error="un-appendable") True :: @@ -528,11 +824,19 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: return self def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-append_to_list-expected_result0 + + .. code-block:: python >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True + monad1-append_to_list-expected_result1 + + .. code-block:: python + >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") True :: @@ -540,11 +844,19 @@ def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: return self def is_err(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-False + + .. code-block:: python >>> Ok(inner=2).is_err() == False True + monad1-True + + .. code-block:: python + >>> Err(error=2).is_err() == True True :: @@ -552,14 +864,26 @@ def is_err(self) -> bool: return False def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-is_even-False + + .. code-block:: python >>> Err(error=1).is_err_and(is_even) == False True + monad1-is_even-True + + .. code-block:: python + >>> Err(error=2).is_err_and(is_even) == True True + monad2-is_even-False + + .. code-block:: python + >>> Ok(inner=2).is_err_and(is_even) == False True :: @@ -567,11 +891,19 @@ def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 return False def is_ok(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-True + + .. code-block:: python >>> Ok(inner=2).is_ok() == True True + monad1-False + + .. code-block:: python + >>> Err(error=2).is_ok() == False True :: @@ -579,14 +911,26 @@ def is_ok(self) -> bool: return True def is_ok_and(self, fn: Callable[[T], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-is_even-False + + .. code-block:: python >>> Ok(inner=1).is_ok_and(is_even) == False True + monad1-is_even-True + + .. code-block:: python + >>> Ok(inner=2).is_ok_and(is_even) == True True + monad2-is_even-False + + .. code-block:: python + >>> Err(error=2).is_ok_and(is_even) == False True :: @@ -596,11 +940,19 @@ def is_ok_and(self, fn: Callable[[T], bool]) -> bool: def map[U, **P]( self, fn: Callable[Concatenate[T, P], U], *args: P.args, **kwargs: P.kwargs ) -> Result[U, Never]: - """.. code-block:: python + """Papertrail examples: + + monad0-add_one-expected_result0 + + .. code-block:: python >>> Ok(inner=1).map(add_one) == Ok(inner=2) True + monad1-add_one-expected_result1 + + .. code-block:: python + >>> Err(error=1).map(add_one) == Err(error=1) True :: @@ -613,11 +965,19 @@ def map_err[F, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[T, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-add_one-expected_result0 + + .. code-block:: python >>> Err(error=1).map_err(add_one) == Err(error=2) True + monad1-add_one-expected_result1 + + .. code-block:: python + >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) True :: @@ -631,11 +991,19 @@ def map_or[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-42-len-3 + + .. code-block:: python >>> Ok(inner="foo").map_or(42, len) == 3 True + monad1-42-len-42 + + .. code-block:: python + >>> Err(error=None).map_or(42, len) == 42 True :: @@ -649,11 +1017,19 @@ def map_or_else[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-get_42-len-3 + + .. code-block:: python >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True + monad1-get_42-len-42 + + .. code-block:: python + >>> Err(error=None).map_or_else(get_42, len) == 42 True :: @@ -661,11 +1037,19 @@ def map_or_else[U, **P]( return fn(self.inner, *args, **kwargs) def ok(self) -> Option[T]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).ok() == Some(inner=2) True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error=2).ok() == Null() True :: @@ -675,17 +1059,33 @@ def ok(self) -> Option[T]: return Some(self.inner) def or_[F](self, res: Result[T, F]) -> Result[T, F]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-res0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True + monad1-res1-expected_result1 + + .. code-block:: python + >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True + monad2-res2-expected_result2 + + .. code-block:: python + >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True + monad3-res3-expected_result3 + + .. code-block:: python + >>> Err(error="foo").or_(Err(error="foo")) == Err(error="foo") True :: @@ -698,14 +1098,26 @@ def or_else[F, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[T, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-get_ok_vikings-expected_result0 + + .. code-block:: python >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True + monad1-get_ok_vikings-expected_result1 + + .. code-block:: python + >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True + monad2-Err-expected_result2 + + .. code-block:: python + >>> Err(error="foo").or_else(Err) == Err(error="foo") True :: @@ -713,14 +1125,26 @@ def or_else[F, **P]( return cast(Result[T, F], self) def transpose(self) -> Option[Result[T, Never]]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0-expected_context0 + + .. code-block:: python >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True + monad1-expected_result1-expected_context1 + + .. code-block:: python + >>> Ok(inner=Null()).transpose() == Null() True + monad2-expected_result2-expected_context2 + + .. code-block:: python + >>> Err(error=None).transpose() == Some(inner=Err(error=None)) True :: @@ -734,7 +1158,11 @@ def transpose(self) -> Option[Result[T, Never]]: raise TypeError("inner must be an `Option` type") def unwrap(self) -> T: - """.. code-block:: python + """Papertrail examples: + + monad0-2-expected_context0 + + .. code-block:: python >>> Ok(inner=2).unwrap() == 2 True @@ -743,7 +1171,11 @@ def unwrap(self) -> T: return self.inner def unwrap_err(self) -> Never: - """.. code-block:: python + """Papertrail examples: + + monad0-failed-expected_context0 + + .. code-block:: python >>> Err(error="failed").unwrap_err() == "failed" True @@ -752,11 +1184,19 @@ def unwrap_err(self) -> Never: raise TypeError("Can't call `unwrap_err` on `Ok`") def unwrap_or(self, default: T) -> T: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-bike-car + + .. code-block:: python >>> Ok(inner="car").unwrap_or("bike") == "car" True + monad1-bike-bike + + .. code-block:: python + >>> Err(error=None).unwrap_or("bike") == "bike" True :: @@ -764,11 +1204,19 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 return self.inner def unwrap_or_else(self, fn: Callable[[Never], T]) -> T: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-get_42-4 + + .. code-block:: python >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True + monad1-get_42-42 + + .. code-block:: python + >>> Err(error=None).unwrap_or_else(get_42) == 42 True :: @@ -789,17 +1237,33 @@ class Err[E](Result[Never, E]): traceback: str = attrs.field(default="", validator=instance_of(str), repr=False) def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. + + .. code-block:: python >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True + Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. + + .. code-block:: python + >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True + Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. + + .. code-block:: python + >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True + Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned. + + .. code-block:: python + >>> Ok(inner=2).and_(Ok(inner="different result type")) == Ok(inner="different result type") True :: @@ -812,14 +1276,26 @@ def and_then[U, F, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[U, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-must_be_less_than_10-expected_result0 + + .. code-block:: python >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True + monad1-must_be_less_than_10-expected_result1 + + .. code-block:: python + >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True + monad2-must_be_less_than_10-expected_result2 + + .. code-block:: python + >>> Err(error="not a number").and_then(must_be_less_than_10) == Err(error="not a number") True :: @@ -827,11 +1303,19 @@ def and_then[U, F, **P]( return cast(Result[U, F], self) def err(self) -> Option[E]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).err() == Null() True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error="Nothing here").err() == Some(inner="Nothing here") True :: @@ -841,7 +1325,11 @@ def err(self) -> Option[E]: return Some(self.error) def expect(self, msg: str) -> Never: - """.. code-block:: python + """Papertrail examples: + + monad0-must be positive-2-expected_context0 + + .. code-block:: python >>> Ok(inner=2).expect("must be positive") == 2 True @@ -850,7 +1338,11 @@ def expect(self, msg: str) -> Never: raise ValueError(msg) def expect_err(self, msg: str) -> E: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-must be err-2-expected_context0 + + .. code-block:: python >>> Err(error=2).expect_err("must be err") == 2 True @@ -859,17 +1351,33 @@ def expect_err(self, msg: str) -> E: # noqa: ARG002 return self.error def flatten(self) -> Result[Never, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True + monad1-expected_result1 + + .. code-block:: python + >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True + monad2-expected_result2 + + .. code-block:: python + >>> Ok(inner=2).flatten() == Ok(inner=2) True + monad3-expected_result3 + + .. code-block:: python + >>> Err(error=None).flatten() == Err(error=None) True :: @@ -877,11 +1385,19 @@ def flatten(self) -> Result[Never, E]: return self def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-append_to_list-expected_result0 + + .. code-block:: python >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True + monad1-append_to_list-expected_result1 + + .. code-block:: python + >>> Err(error="un-appendable").inspect(append_to_list) == Err(error="un-appendable") True :: @@ -889,11 +1405,19 @@ def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG return self def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-append_to_list-expected_result0 + + .. code-block:: python >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True + monad1-append_to_list-expected_result1 + + .. code-block:: python + >>> Ok(inner="un-appendable").inspect_err(append_to_list) == Ok(inner="un-appendable") True :: @@ -902,11 +1426,19 @@ def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: return self def is_err(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-False + + .. code-block:: python >>> Ok(inner=2).is_err() == False True + monad1-True + + .. code-block:: python + >>> Err(error=2).is_err() == True True :: @@ -914,14 +1446,26 @@ def is_err(self) -> bool: return True def is_err_and(self, fn: Callable[[E], bool]) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-is_even-False + + .. code-block:: python >>> Err(error=1).is_err_and(is_even) == False True + monad1-is_even-True + + .. code-block:: python + >>> Err(error=2).is_err_and(is_even) == True True + monad2-is_even-False + + .. code-block:: python + >>> Ok(inner=2).is_err_and(is_even) == False True :: @@ -929,11 +1473,19 @@ def is_err_and(self, fn: Callable[[E], bool]) -> bool: return fn(self.error) def is_ok(self) -> bool: - """.. code-block:: python + """Papertrail examples: + + monad0-True + + .. code-block:: python >>> Ok(inner=2).is_ok() == True True + monad1-False + + .. code-block:: python + >>> Err(error=2).is_ok() == False True :: @@ -941,14 +1493,26 @@ def is_ok(self) -> bool: return False def is_ok_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 - """.. code-block:: python + """Papertrail examples: + + monad0-is_even-False + + .. code-block:: python >>> Ok(inner=1).is_ok_and(is_even) == False True + monad1-is_even-True + + .. code-block:: python + >>> Ok(inner=2).is_ok_and(is_even) == True True + monad2-is_even-False + + .. code-block:: python + >>> Err(error=2).is_ok_and(is_even) == False True :: @@ -961,11 +1525,19 @@ def map[U, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> Result[U, E]: - """.. code-block:: python + """Papertrail examples: + + monad0-add_one-expected_result0 + + .. code-block:: python >>> Ok(inner=1).map(add_one) == Ok(inner=2) True + monad1-add_one-expected_result1 + + .. code-block:: python + >>> Err(error=1).map(add_one) == Err(error=1) True :: @@ -975,11 +1547,19 @@ def map[U, **P]( def map_err[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs ) -> Result[Never, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-add_one-expected_result0 + + .. code-block:: python >>> Err(error=1).map_err(add_one) == Err(error=2) True + monad1-add_one-expected_result1 + + .. code-block:: python + >>> Ok(inner=1).map_err(add_one) == Ok(inner=1) True :: @@ -995,11 +1575,19 @@ def map_or[U, **P]( *args: P.args, # noqa: ARG002 **kwargs: P.kwargs, # noqa: ARG002 ) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-42-len-3 + + .. code-block:: python >>> Ok(inner="foo").map_or(42, len) == 3 True + monad1-42-len-42 + + .. code-block:: python + >>> Err(error=None).map_or(42, len) == 42 True :: @@ -1013,11 +1601,19 @@ def map_or_else[U, **P]( *args: P.args, **kwargs: P.kwargs, ) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-get_42-len-3 + + .. code-block:: python >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True + monad1-get_42-len-42 + + .. code-block:: python + >>> Err(error=None).map_or_else(get_42, len) == 42 True :: @@ -1025,11 +1621,19 @@ def map_or_else[U, **P]( return default(*args, **kwargs) def ok(self) -> Option[Never]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).ok() == Some(inner=2) True + monad1-expected_result1 + + .. code-block:: python + >>> Err(error=2).ok() == Null() True :: @@ -1039,17 +1643,33 @@ def ok(self) -> Option[Never]: return Null() def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-res0-expected_result0 + + .. code-block:: python >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True + monad1-res1-expected_result1 + + .. code-block:: python + >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True + monad2-res2-expected_result2 + + .. code-block:: python + >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True + monad3-res3-expected_result3 + + .. code-block:: python + >>> Err(error="foo").or_(Err(error="foo")) == Err(error="foo") True :: @@ -1059,14 +1679,26 @@ def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: def or_else[F, **P]( self, fn: Callable[Concatenate[E, P], F], *args: P.args, **kwargs: P.kwargs ) -> Result[Never, F]: - """.. code-block:: python + """Papertrail examples: + + monad0-get_ok_vikings-expected_result0 + + .. code-block:: python >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True + monad1-get_ok_vikings-expected_result1 + + .. code-block:: python + >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True + monad2-Err-expected_result2 + + .. code-block:: python + >>> Err(error="foo").or_else(Err) == Err(error="foo") True :: @@ -1074,14 +1706,26 @@ def or_else[F, **P]( return cast(Result[Never, F], fn(self.error, *args, **kwargs)) def transpose(self) -> Option[Result[Never, E]]: - """.. code-block:: python + """Papertrail examples: + + monad0-expected_result0-expected_context0 + + .. code-block:: python >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True + monad1-expected_result1-expected_context1 + + .. code-block:: python + >>> Ok(inner=Null()).transpose() == Null() True + monad2-expected_result2-expected_context2 + + .. code-block:: python + >>> Err(error=None).transpose() == Some(inner=Err(error=None)) True :: @@ -1091,7 +1735,11 @@ def transpose(self) -> Option[Result[Never, E]]: return Some(self) def unwrap(self) -> Never: - """.. code-block:: python + """Papertrail examples: + + monad0-2-expected_context0 + + .. code-block:: python >>> Ok(inner=2).unwrap() == 2 True @@ -1102,7 +1750,11 @@ def unwrap(self) -> Never: raise TypeError("Can't call `unwrap` on `Err`") def unwrap_err(self) -> E: - """.. code-block:: python + """Papertrail examples: + + monad0-failed-expected_context0 + + .. code-block:: python >>> Err(error="failed").unwrap_err() == "failed" True @@ -1111,11 +1763,19 @@ def unwrap_err(self) -> E: return self.error def unwrap_or[U](self, default: U) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-bike-car + + .. code-block:: python >>> Ok(inner="car").unwrap_or("bike") == "car" True + monad1-bike-bike + + .. code-block:: python + >>> Err(error=None).unwrap_or("bike") == "bike" True :: @@ -1123,11 +1783,19 @@ def unwrap_or[U](self, default: U) -> U: return default def unwrap_or_else[U](self, fn: Callable[[E], U]) -> U: - """.. code-block:: python + """Papertrail examples: + + monad0-get_42-4 + + .. code-block:: python >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True + monad1-get_42-42 + + .. code-block:: python + >>> Err(error=None).unwrap_or_else(get_42) == 42 True :: diff --git a/tests/monads/test_result_v2.py b/tests/monads/test_result_v2.py index 4db1914..9cc1027 100644 --- a/tests/monads/test_result_v2.py +++ b/tests/monads/test_result_v2.py @@ -12,14 +12,34 @@ @pytest.mark.parametrize( ("monad", "res", "expected_result"), [ - pytest.param(Ok(2), Err("late error"), Err("late error")), - pytest.param(Err("early error"), Ok("foo"), Err("early error")), - pytest.param(Err("not a 2"), Err("late error"), Err("not a 2")), - pytest.param(Ok(2), Ok("different result type"), Ok("different result type")), + pytest.param( + Ok(2), + Err("late error"), + Err("late error"), + id="Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``.", + ), + pytest.param( + Err("early error"), + Ok("foo"), + Err("early error"), + id="Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``.", + ), + pytest.param( + Err("not a 2"), + Err("late error"), + Err("not a 2"), + id="Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded.", + ), + pytest.param( + Ok(2), + Ok("different result type"), + Ok("different result type"), + id="Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned.", + ), ], ) -def test_and_(monad: Result, res, expected_result) -> None: - assert example(monad.and_, res) == expected_result +def test_and_(request, monad: Result, res, expected_result) -> None: + assert example(monad.and_, res, description=request.node.callspec.id) == expected_result def must_be_less_than_10(x: int) -> Result[int, str]: @@ -34,15 +54,15 @@ def must_be_less_than_10(x: int) -> Result[int, str]: pytest.param(Err("not a number"), must_be_less_than_10, Err("not a number")), ], ) -def test_and_then(monad: Result, fn, expected_result) -> None: - assert example(monad.and_then, fn) == expected_result +def test_and_then(request, monad: Result, fn, expected_result) -> None: + assert example(monad.and_then, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), Ok(2)), pytest.param(Err(2), Err(2))] ) -def test_cloned(monad: Result, expected_result) -> None: - assert example(monad.cloned) == expected_result +def test_cloned(request, monad: Result, expected_result) -> None: + assert example(monad.cloned, description=request.node.callspec.id) == expected_result assert id(monad) != id(expected_result) @@ -50,8 +70,8 @@ def test_cloned(monad: Result, expected_result) -> None: ("monad", "expected_result"), [pytest.param(Ok(2), Null()), pytest.param(Err("Nothing here"), Some("Nothing here"))], ) -def test_err(monad: Result, expected_result) -> None: - assert example(monad.err) == expected_result +def test_err(request, monad: Result, expected_result) -> None: + assert example(monad.err, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -61,9 +81,9 @@ def test_err(monad: Result, expected_result) -> None: pytest.param(Err(), "must be positive", None, pytest.raises(ValueError)), ], ) -def test_expect(monad: Result, msg, expected_result, expected_context) -> None: +def test_expect(request, monad: Result, msg, expected_result, expected_context) -> None: with expected_context: - assert example(monad.expect, msg) == expected_result + assert example(monad.expect, msg, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -73,9 +93,11 @@ def test_expect(monad: Result, msg, expected_result, expected_context) -> None: pytest.param(Ok(), "must be err", None, pytest.raises(ValueError)), ], ) -def test_expect_err(monad: Result, msg, expected_result, expected_context) -> None: +def test_expect_err(request, monad: Result, msg, expected_result, expected_context) -> None: with expected_context: - assert example(monad.expect_err, msg) == expected_result + assert ( + example(monad.expect_err, msg, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( @@ -87,8 +109,8 @@ def test_expect_err(monad: Result, msg, expected_result, expected_context) -> No pytest.param(Err(), Err()), ], ) -def test_flatten(monad: Result, expected_result) -> None: - assert example(monad.flatten) == expected_result +def test_flatten(request, monad: Result, expected_result) -> None: + assert example(monad.flatten, description=request.node.callspec.id) == expected_result def append_to_list(x) -> None: @@ -102,8 +124,8 @@ def append_to_list(x) -> None: pytest.param(Err("un-appendable"), append_to_list, Err("un-appendable")), ], ) -def test_inspect(monad: Result, fn, expected_result) -> None: - assert example(monad.inspect, fn) == expected_result +def test_inspect(request, monad: Result, fn, expected_result) -> None: + assert example(monad.inspect, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -113,15 +135,15 @@ def test_inspect(monad: Result, fn, expected_result) -> None: pytest.param(Ok("un-appendable"), append_to_list, Ok("un-appendable")), ], ) -def test_inspect_err(monad: Result, fn, expected_result) -> None: - assert example(monad.inspect_err, fn) == expected_result +def test_inspect_err(request, monad: Result, fn, expected_result) -> None: + assert example(monad.inspect_err, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), False), pytest.param(Err(2), True)] ) -def test_is_err(monad: Result, expected_result) -> None: - assert example(monad.is_err) == expected_result +def test_is_err(request, monad: Result, expected_result) -> None: + assert example(monad.is_err, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -132,15 +154,15 @@ def test_is_err(monad: Result, expected_result) -> None: pytest.param(Ok(2), is_even, False), ], ) -def test_is_err_and(monad: Result, fn, expected_result) -> None: - assert example(monad.is_err_and, fn) == expected_result +def test_is_err_and(request, monad: Result, fn, expected_result) -> None: + assert example(monad.is_err_and, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), True), pytest.param(Err(2), False)] ) -def test_is_ok(monad: Result, expected_result) -> None: - assert example(monad.is_ok) == expected_result +def test_is_ok(request, monad: Result, expected_result) -> None: + assert example(monad.is_ok, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -151,47 +173,52 @@ def test_is_ok(monad: Result, expected_result) -> None: pytest.param(Err(2), is_even, False), ], ) -def test_is_ok_and(monad: Result, fn, expected_result) -> None: - assert example(monad.is_ok_and, fn) == expected_result +def test_is_ok_and(request, monad: Result, fn, expected_result) -> None: + assert example(monad.is_ok_and, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [pytest.param(Ok(1), add_one, Ok(2)), pytest.param(Err(1), add_one, Err(1))], ) -def test_map(monad: Result, fn, expected_result) -> None: - assert example(monad.map, fn) == expected_result +def test_map(request, monad: Result, fn, expected_result) -> None: + assert example(monad.map, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [pytest.param(Err(1), add_one, Err(2)), pytest.param(Ok(1), add_one, Ok(1))], ) -def test_map_err(monad: Result, fn, expected_result) -> None: - assert example(monad.map_err, fn) == expected_result +def test_map_err(request, monad: Result, fn, expected_result) -> None: + assert example(monad.map_err, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), [pytest.param(Ok("foo"), 42, len, 3), pytest.param(Err(), 42, len, 42)], ) -def test_map_or(monad: Result, default, fn, expected_result) -> None: - assert example(monad.map_or, default, fn) == expected_result +def test_map_or(request, monad: Result, default, fn, expected_result) -> None: + assert ( + example(monad.map_or, default, fn, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), [pytest.param(Ok("foo"), get_42, len, 3), pytest.param(Err(), get_42, len, 42)], ) -def test_map_or_else(monad: Result, default, fn, expected_result) -> None: - assert example(monad.map_or_else, default, fn) == expected_result +def test_map_or_else(request, monad: Result, default, fn, expected_result) -> None: + assert ( + example(monad.map_or_else, default, fn, description=request.node.callspec.id) + == expected_result + ) @pytest.mark.parametrize( ("monad", "expected_result"), [pytest.param(Ok(2), Some(2)), pytest.param(Err(2), Null())] ) -def test_ok(monad: Result, expected_result) -> None: - assert example(monad.ok) == expected_result +def test_ok(request, monad: Result, expected_result) -> None: + assert example(monad.ok, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -203,8 +230,8 @@ def test_ok(monad: Result, expected_result) -> None: pytest.param(Err("foo"), Err("foo"), Err("foo")), ], ) -def test_or_(monad: Result, res, expected_result) -> None: - assert example(monad.or_, res) == expected_result +def test_or_(request, monad: Result, res, expected_result) -> None: + assert example(monad.or_, res, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -215,8 +242,8 @@ def test_or_(monad: Result, res, expected_result) -> None: pytest.param(Err("foo"), Err, Err("foo")), ], ) -def test_or_else(monad: Result, fn, expected_result) -> None: - assert example(monad.or_else, fn) == expected_result +def test_or_else(request, monad: Result, fn, expected_result) -> None: + assert example(monad.or_else, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -228,18 +255,18 @@ def test_or_else(monad: Result, fn, expected_result) -> None: pytest.param(Ok(2), None, pytest.raises(TypeError)), ], ) -def test_transpose(monad: Result, expected_result, expected_context) -> None: +def test_transpose(request, monad: Result, expected_result, expected_context) -> None: with expected_context: - assert example(monad.transpose) == expected_result + assert example(monad.transpose, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result", "expected_context"), [pytest.param(Ok(2), 2, nullcontext()), pytest.param(Err(), None, pytest.raises(TypeError))], ) -def test_unwrap(monad: Result, expected_result, expected_context) -> None: +def test_unwrap(request, monad: Result, expected_result, expected_context) -> None: with expected_context: - assert example(monad.unwrap) == expected_result + assert example(monad.unwrap, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( @@ -249,22 +276,26 @@ def test_unwrap(monad: Result, expected_result, expected_context) -> None: pytest.param(Ok(2), None, pytest.raises(TypeError)), ], ) -def test_unwrap_err(monad: Result, expected_result, expected_context) -> None: +def test_unwrap_err(request, monad: Result, expected_result, expected_context) -> None: with expected_context: - assert example(monad.unwrap_err) == expected_result + assert example(monad.unwrap_err, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "default", "expected_result"), [pytest.param(Ok("car"), "bike", "car"), pytest.param(Err(), "bike", "bike")], ) -def test_unwrap_or(monad: Result, default, expected_result) -> None: - assert example(monad.unwrap_or, default) == expected_result +def test_unwrap_or(request, monad: Result, default, expected_result) -> None: + assert ( + example(monad.unwrap_or, default, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [pytest.param(Ok(4), get_42, 4), pytest.param(Err(), get_42, 42)], ) -def test_unwrap_or_else(monad: Result, fn, expected_result) -> None: - assert example(monad.unwrap_or_else, fn) == expected_result +def test_unwrap_or_else(request, monad: Result, fn, expected_result) -> None: + assert ( + example(monad.unwrap_or_else, fn, description=request.node.callspec.id) == expected_result + ) From a8a71dd2f2d28ee82de158565b938862f194a907 Mon Sep 17 00:00:00 2001 From: ed cuss Date: Sat, 26 Sep 2026 19:56:48 +0100 Subject: [PATCH 09/11] docs add docs from tests for result_v2 --- src/danom/_monads/_result_v2.py | 328 ++++++++++++++--------------- tests/monads/test_result_v2.py | 355 +++++++++++++++++++++++++++----- 2 files changed, 470 insertions(+), 213 deletions(-) diff --git a/src/danom/_monads/_result_v2.py b/src/danom/_monads/_result_v2.py index 6d6d66a..3be799b 100644 --- a/src/danom/_monads/_result_v2.py +++ b/src/danom/_monads/_result_v2.py @@ -65,21 +65,21 @@ def result_unwrap(result: Result[T, E]) -> T: def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: """Papertrail examples: - Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. + If you call ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. .. code-block:: python >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. + If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. .. code-block:: python >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. + If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. .. code-block:: python @@ -102,21 +102,21 @@ def and_then[U, F, **P]( ) -> Result[U, F]: """Papertrail examples: - monad0-must_be_less_than_10-expected_result0 + An ``Ok`` passed to ``and_then`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - monad1-must_be_less_than_10-expected_result1 + When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result. .. code-block:: python >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - monad2-must_be_less_than_10-expected_result2 + An existing ``Err`` passes through ``and_then`` unchanged, without calling the function. .. code-block:: python @@ -129,14 +129,14 @@ def and_then[U, F, **P]( def cloned(self) -> Result[T, E]: """Papertrail examples: - monad0-expected_result0 + Cloning an ``Ok`` creates a separate ``Ok`` with the same value. .. code-block:: python >>> Ok(inner=2).cloned() == Ok(inner=2) True - monad1-expected_result1 + Cloning an ``Err`` creates a separate ``Err`` with the same value. .. code-block:: python @@ -150,14 +150,14 @@ def cloned(self) -> Result[T, E]: def err(self) -> Option[E]: """Papertrail examples: - monad0-expected_result0 + An ``Ok`` has no error, so ``err`` returns ``Null``. .. code-block:: python >>> Ok(inner=2).err() == Null() True - monad1-expected_result1 + If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned. .. code-block:: python @@ -171,7 +171,7 @@ def err(self) -> Option[E]: def expect(self, msg: str) -> T: """Papertrail examples: - monad0-must be positive-2-expected_context0 + ``expect`` extracts the value from an ``Ok``. .. code-block:: python @@ -185,7 +185,7 @@ def expect(self, msg: str) -> T: def expect_err(self, msg: str) -> E: """Papertrail examples: - monad0-must be err-2-expected_context0 + ``expect_err`` extracts the error from an ``Err``. .. code-block:: python @@ -199,28 +199,28 @@ def expect_err(self, msg: str) -> E: def flatten(self) -> Result[T, E]: """Papertrail examples: - monad0-expected_result0 + If you call ``flatten`` on three nested ``Ok`` values then two nested ``Ok`` values are returned. Only the outer monad is removed .. code-block:: python >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True - monad1-expected_result1 + This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad. .. code-block:: python >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True - monad2-expected_result2 + Calling ``flatten`` if the inner is not a monad of the same type is a no-op. .. code-block:: python >>> Ok(inner=2).flatten() == Ok(inner=2) True - monad3-expected_result3 + An ``Err`` passes through ``flatten`` unchanged. .. code-block:: python @@ -234,14 +234,14 @@ def flatten(self) -> Result[T, E]: def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: """Papertrail examples: - monad0-append_to_list-expected_result0 + If you call ``inspect`` on an ``Ok`` then the original ``Ok`` is returned after the function is called, this is used for logging or side outputs that shouldn't disrupt the existing flow. .. code-block:: python >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - monad1-append_to_list-expected_result1 + If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called. .. code-block:: python @@ -255,14 +255,14 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: """Papertrail examples: - monad0-append_to_list-expected_result0 + If you call ``inspect_err`` on an ``Err`` then the original ``Err`` is returned after the function is called, it's essentially the inverse of ``inspect``. .. code-block:: python >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True - monad1-append_to_list-expected_result1 + Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called. .. code-block:: python @@ -276,14 +276,14 @@ def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: def is_err(self) -> bool: """Papertrail examples: - monad0-False + ``is_err`` reports ``False`` for an ``Ok``. .. code-block:: python >>> Ok(inner=2).is_err() == False True - monad1-True + For an ``Err``, ``is_err`` reports ``True``. .. code-block:: python @@ -297,21 +297,21 @@ def is_err(self) -> bool: def is_err_and(self, fn: Callable[[E], bool]) -> bool: """Papertrail examples: - monad0-is_even-False + ``is_err_and`` requires both the monad to be an ``Err`` and the returned value of the passed in callable to be ``True``, for example, if you called ``is_err_and`` with a function that checks for evenness on an ``Err(1)`` then the result is ``False``. .. code-block:: python >>> Err(error=1).is_err_and(is_even) == False True - monad1-is_even-True + Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned. .. code-block:: python >>> Err(error=2).is_err_and(is_even) == True True - monad2-is_even-False + Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called. .. code-block:: python @@ -325,14 +325,14 @@ def is_err_and(self, fn: Callable[[E], bool]) -> bool: def is_ok(self) -> bool: """Papertrail examples: - monad0-True + ``is_ok`` reports ``True`` for an ``Ok``. .. code-block:: python >>> Ok(inner=2).is_ok() == True True - monad1-False + For an ``Err``, ``is_ok`` reports ``False``. .. code-block:: python @@ -346,21 +346,21 @@ def is_ok(self) -> bool: def is_ok_and(self, fn: Callable[[T], bool]) -> bool: """Papertrail examples: - monad0-is_even-False + When the predicate rejects an ``Ok``, ``is_ok_and`` reports ``False``. .. code-block:: python >>> Ok(inner=1).is_ok_and(is_even) == False True - monad1-is_even-True + When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``. .. code-block:: python >>> Ok(inner=2).is_ok_and(is_even) == True True - monad2-is_even-False + An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -376,14 +376,14 @@ def map[U, **P]( ) -> Result[U, E]: """Papertrail examples: - monad0-add_one-expected_result0 + Mapping an ``Ok`` applies the function and wraps its result in a new ``Ok``. .. code-block:: python >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - monad1-add_one-expected_result1 + Mapping an ``Err`` leaves it unchanged and skips the function. .. code-block:: python @@ -399,14 +399,14 @@ def map_err[F, **P]( ) -> Result[T, F]: """Papertrail examples: - monad0-add_one-expected_result0 + Mapping an ``Err`` applies the function and wraps its result in a new ``Err``. .. code-block:: python >>> Err(error=1).map_err(add_one) == Err(error=2) True - monad1-add_one-expected_result1 + Mapping an ``Ok`` leaves it unchanged and skips the function. .. code-block:: python @@ -422,14 +422,14 @@ def map_or[U, **P]( ) -> U: """Papertrail examples: - monad0-42-len-3 + For an ``Ok``, ``map_or`` uses the function result instead of the default. .. code-block:: python >>> Ok(inner="foo").map_or(42, len) == 3 True - monad1-42-len-42 + For an ``Err``, ``map_or`` returns the supplied default. .. code-block:: python @@ -449,14 +449,14 @@ def map_or_else[U, **P]( ) -> U: """Papertrail examples: - monad0-get_42-len-3 + An ``Ok`` makes ``map_or_else`` use the mapping function. .. code-block:: python >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True - monad1-get_42-len-42 + An ``Err`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -470,14 +470,14 @@ def map_or_else[U, **P]( def ok(self) -> Option[T]: """Papertrail examples: - monad0-expected_result0 + An ``Ok`` converts to ``Some`` through ``ok``. .. code-block:: python >>> Ok(inner=2).ok() == Some(inner=2) True - monad1-expected_result1 + An ``Err`` converts to ``Null`` through ``ok``. .. code-block:: python @@ -491,28 +491,28 @@ def ok(self) -> Option[T]: def or_[F](self, res: Result[T, F]) -> Result[T, F]: """Papertrail examples: - monad0-res0-expected_result0 + An ``Ok`` keeps its value when ``or_`` receives an ``Err``. .. code-block:: python >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - monad1-res1-expected_result1 + An ``Err`` gives way to an ``Ok`` passed to ``or_``. .. code-block:: python >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True - monad2-res2-expected_result2 + When both values are ``Ok``, ``or_`` keeps the first one. .. code-block:: python >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - monad3-res3-expected_result3 + When both values are ``Err``, ``or_`` keeps the first error. .. code-block:: python @@ -528,21 +528,21 @@ def or_else[F, **P]( ) -> Result[T, F]: """Papertrail examples: - monad0-get_ok_vikings-expected_result0 + An ``Ok`` passes through ``or_else`` without calling the function. .. code-block:: python >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - monad1-get_ok_vikings-expected_result1 + For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - monad2-Err-expected_result2 + If the fallback also produces an ``Err``, ``or_else`` keeps the original error. .. code-block:: python @@ -556,21 +556,21 @@ def or_else[F, **P]( def transpose(self) -> Option[Result[T, E]]: """Papertrail examples: - monad0-expected_result0-expected_context0 + Transposing ``Ok(Some(value))`` produces ``Some(Ok(value))``. .. code-block:: python >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True - monad1-expected_result1-expected_context1 + Transposing ``Ok(Null())`` produces ``Null``. .. code-block:: python >>> Ok(inner=Null()).transpose() == Null() True - monad2-expected_result2-expected_context2 + Transposing an ``Err`` wraps it in ``Some``. .. code-block:: python @@ -584,7 +584,7 @@ def transpose(self) -> Option[Result[T, E]]: def unwrap(self) -> T: """Papertrail examples: - monad0-2-expected_context0 + ``unwrap`` extracts the value from an ``Ok``. .. code-block:: python @@ -598,7 +598,7 @@ def unwrap(self) -> T: def unwrap_err(self) -> E: """Papertrail examples: - monad0-failed-expected_context0 + ``unwrap_err`` extracts the error from an ``Err``. .. code-block:: python @@ -612,14 +612,14 @@ def unwrap_err(self) -> E: def unwrap_or(self, default: T) -> T: """Papertrail examples: - monad0-bike-car + With an ``Ok``, ``unwrap_or`` returns the value and ignores the default. .. code-block:: python >>> Ok(inner="car").unwrap_or("bike") == "car" True - monad1-bike-bike + With an ``Err``, ``unwrap_or`` returns the default. .. code-block:: python @@ -633,14 +633,14 @@ def unwrap_or(self, default: T) -> T: def unwrap_or_else(self, fn: Callable[[E], T]) -> T: """Papertrail examples: - monad0-get_42-4 + An ``Ok`` makes ``unwrap_or_else`` return its value without calling the function. .. code-block:: python >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - monad1-get_42-42 + An ``Err`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -658,21 +658,21 @@ class Ok[T](Result[T, Never]): def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: """Papertrail examples: - Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. + If you call ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. .. code-block:: python >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. + If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. .. code-block:: python >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. + If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. .. code-block:: python @@ -694,21 +694,21 @@ def and_then[U, E, **P]( ) -> Result[U, E]: """Papertrail examples: - monad0-must_be_less_than_10-expected_result0 + An ``Ok`` passed to ``and_then`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - monad1-must_be_less_than_10-expected_result1 + When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result. .. code-block:: python >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - monad2-must_be_less_than_10-expected_result2 + An existing ``Err`` passes through ``and_then`` unchanged, without calling the function. .. code-block:: python @@ -721,14 +721,14 @@ def and_then[U, E, **P]( def err[E](self) -> Option[E]: """Papertrail examples: - monad0-expected_result0 + An ``Ok`` has no error, so ``err`` returns ``Null``. .. code-block:: python >>> Ok(inner=2).err() == Null() True - monad1-expected_result1 + If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned. .. code-block:: python @@ -743,7 +743,7 @@ def err[E](self) -> Option[E]: def expect(self, msg: str) -> T: # noqa: ARG002 """Papertrail examples: - monad0-must be positive-2-expected_context0 + ``expect`` extracts the value from an ``Ok``. .. code-block:: python @@ -756,7 +756,7 @@ def expect(self, msg: str) -> T: # noqa: ARG002 def expect_err(self, msg: str) -> Never: """Papertrail examples: - monad0-must be err-2-expected_context0 + ``expect_err`` extracts the error from an ``Err``. .. code-block:: python @@ -769,28 +769,28 @@ def expect_err(self, msg: str) -> Never: def flatten(self) -> Result[T, Never]: """Papertrail examples: - monad0-expected_result0 + If you call ``flatten`` on three nested ``Ok`` values then two nested ``Ok`` values are returned. Only the outer monad is removed .. code-block:: python >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True - monad1-expected_result1 + This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad. .. code-block:: python >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True - monad2-expected_result2 + Calling ``flatten`` if the inner is not a monad of the same type is a no-op. .. code-block:: python >>> Ok(inner=2).flatten() == Ok(inner=2) True - monad3-expected_result3 + An ``Err`` passes through ``flatten`` unchanged. .. code-block:: python @@ -805,14 +805,14 @@ def flatten(self) -> Result[T, Never]: def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: """Papertrail examples: - monad0-append_to_list-expected_result0 + If you call ``inspect`` on an ``Ok`` then the original ``Ok`` is returned after the function is called, this is used for logging or side outputs that shouldn't disrupt the existing flow. .. code-block:: python >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - monad1-append_to_list-expected_result1 + If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called. .. code-block:: python @@ -826,14 +826,14 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: ARG002 """Papertrail examples: - monad0-append_to_list-expected_result0 + If you call ``inspect_err`` on an ``Err`` then the original ``Err`` is returned after the function is called, it's essentially the inverse of ``inspect``. .. code-block:: python >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True - monad1-append_to_list-expected_result1 + Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called. .. code-block:: python @@ -846,14 +846,14 @@ def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: def is_err(self) -> bool: """Papertrail examples: - monad0-False + ``is_err`` reports ``False`` for an ``Ok``. .. code-block:: python >>> Ok(inner=2).is_err() == False True - monad1-True + For an ``Err``, ``is_err`` reports ``True``. .. code-block:: python @@ -866,21 +866,21 @@ def is_err(self) -> bool: def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 """Papertrail examples: - monad0-is_even-False + ``is_err_and`` requires both the monad to be an ``Err`` and the returned value of the passed in callable to be ``True``, for example, if you called ``is_err_and`` with a function that checks for evenness on an ``Err(1)`` then the result is ``False``. .. code-block:: python >>> Err(error=1).is_err_and(is_even) == False True - monad1-is_even-True + Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned. .. code-block:: python >>> Err(error=2).is_err_and(is_even) == True True - monad2-is_even-False + Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called. .. code-block:: python @@ -893,14 +893,14 @@ def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 def is_ok(self) -> bool: """Papertrail examples: - monad0-True + ``is_ok`` reports ``True`` for an ``Ok``. .. code-block:: python >>> Ok(inner=2).is_ok() == True True - monad1-False + For an ``Err``, ``is_ok`` reports ``False``. .. code-block:: python @@ -913,21 +913,21 @@ def is_ok(self) -> bool: def is_ok_and(self, fn: Callable[[T], bool]) -> bool: """Papertrail examples: - monad0-is_even-False + When the predicate rejects an ``Ok``, ``is_ok_and`` reports ``False``. .. code-block:: python >>> Ok(inner=1).is_ok_and(is_even) == False True - monad1-is_even-True + When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``. .. code-block:: python >>> Ok(inner=2).is_ok_and(is_even) == True True - monad2-is_even-False + An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -942,14 +942,14 @@ def map[U, **P]( ) -> Result[U, Never]: """Papertrail examples: - monad0-add_one-expected_result0 + Mapping an ``Ok`` applies the function and wraps its result in a new ``Ok``. .. code-block:: python >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - monad1-add_one-expected_result1 + Mapping an ``Err`` leaves it unchanged and skips the function. .. code-block:: python @@ -967,14 +967,14 @@ def map_err[F, **P]( ) -> Result[T, F]: """Papertrail examples: - monad0-add_one-expected_result0 + Mapping an ``Err`` applies the function and wraps its result in a new ``Err``. .. code-block:: python >>> Err(error=1).map_err(add_one) == Err(error=2) True - monad1-add_one-expected_result1 + Mapping an ``Ok`` leaves it unchanged and skips the function. .. code-block:: python @@ -993,14 +993,14 @@ def map_or[U, **P]( ) -> U: """Papertrail examples: - monad0-42-len-3 + For an ``Ok``, ``map_or`` uses the function result instead of the default. .. code-block:: python >>> Ok(inner="foo").map_or(42, len) == 3 True - monad1-42-len-42 + For an ``Err``, ``map_or`` returns the supplied default. .. code-block:: python @@ -1019,14 +1019,14 @@ def map_or_else[U, **P]( ) -> U: """Papertrail examples: - monad0-get_42-len-3 + An ``Ok`` makes ``map_or_else`` use the mapping function. .. code-block:: python >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True - monad1-get_42-len-42 + An ``Err`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -1039,14 +1039,14 @@ def map_or_else[U, **P]( def ok(self) -> Option[T]: """Papertrail examples: - monad0-expected_result0 + An ``Ok`` converts to ``Some`` through ``ok``. .. code-block:: python >>> Ok(inner=2).ok() == Some(inner=2) True - monad1-expected_result1 + An ``Err`` converts to ``Null`` through ``ok``. .. code-block:: python @@ -1061,28 +1061,28 @@ def ok(self) -> Option[T]: def or_[F](self, res: Result[T, F]) -> Result[T, F]: # noqa: ARG002 """Papertrail examples: - monad0-res0-expected_result0 + An ``Ok`` keeps its value when ``or_`` receives an ``Err``. .. code-block:: python >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - monad1-res1-expected_result1 + An ``Err`` gives way to an ``Ok`` passed to ``or_``. .. code-block:: python >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True - monad2-res2-expected_result2 + When both values are ``Ok``, ``or_`` keeps the first one. .. code-block:: python >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - monad3-res3-expected_result3 + When both values are ``Err``, ``or_`` keeps the first error. .. code-block:: python @@ -1100,21 +1100,21 @@ def or_else[F, **P]( ) -> Result[T, F]: """Papertrail examples: - monad0-get_ok_vikings-expected_result0 + An ``Ok`` passes through ``or_else`` without calling the function. .. code-block:: python >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - monad1-get_ok_vikings-expected_result1 + For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - monad2-Err-expected_result2 + If the fallback also produces an ``Err``, ``or_else`` keeps the original error. .. code-block:: python @@ -1127,21 +1127,21 @@ def or_else[F, **P]( def transpose(self) -> Option[Result[T, Never]]: """Papertrail examples: - monad0-expected_result0-expected_context0 + Transposing ``Ok(Some(value))`` produces ``Some(Ok(value))``. .. code-block:: python >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True - monad1-expected_result1-expected_context1 + Transposing ``Ok(Null())`` produces ``Null``. .. code-block:: python >>> Ok(inner=Null()).transpose() == Null() True - monad2-expected_result2-expected_context2 + Transposing an ``Err`` wraps it in ``Some``. .. code-block:: python @@ -1160,7 +1160,7 @@ def transpose(self) -> Option[Result[T, Never]]: def unwrap(self) -> T: """Papertrail examples: - monad0-2-expected_context0 + ``unwrap`` extracts the value from an ``Ok``. .. code-block:: python @@ -1173,7 +1173,7 @@ def unwrap(self) -> T: def unwrap_err(self) -> Never: """Papertrail examples: - monad0-failed-expected_context0 + ``unwrap_err`` extracts the error from an ``Err``. .. code-block:: python @@ -1186,14 +1186,14 @@ def unwrap_err(self) -> Never: def unwrap_or(self, default: T) -> T: # noqa: ARG002 """Papertrail examples: - monad0-bike-car + With an ``Ok``, ``unwrap_or`` returns the value and ignores the default. .. code-block:: python >>> Ok(inner="car").unwrap_or("bike") == "car" True - monad1-bike-bike + With an ``Err``, ``unwrap_or`` returns the default. .. code-block:: python @@ -1206,14 +1206,14 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 def unwrap_or_else(self, fn: Callable[[Never], T]) -> T: # noqa: ARG002 """Papertrail examples: - monad0-get_42-4 + An ``Ok`` makes ``unwrap_or_else`` return its value without calling the function. .. code-block:: python >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - monad1-get_42-42 + An ``Err`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -1239,21 +1239,21 @@ class Err[E](Result[Never, E]): def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: # noqa: ARG002 """Papertrail examples: - Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. + If you call ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``. .. code-block:: python >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. + If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. .. code-block:: python >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. + If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. .. code-block:: python @@ -1278,21 +1278,21 @@ def and_then[U, F, **P]( ) -> Result[U, F]: """Papertrail examples: - monad0-must_be_less_than_10-expected_result0 + An ``Ok`` passed to ``and_then`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - monad1-must_be_less_than_10-expected_result1 + When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result. .. code-block:: python >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - monad2-must_be_less_than_10-expected_result2 + An existing ``Err`` passes through ``and_then`` unchanged, without calling the function. .. code-block:: python @@ -1305,14 +1305,14 @@ def and_then[U, F, **P]( def err(self) -> Option[E]: """Papertrail examples: - monad0-expected_result0 + An ``Ok`` has no error, so ``err`` returns ``Null``. .. code-block:: python >>> Ok(inner=2).err() == Null() True - monad1-expected_result1 + If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned. .. code-block:: python @@ -1327,7 +1327,7 @@ def err(self) -> Option[E]: def expect(self, msg: str) -> Never: """Papertrail examples: - monad0-must be positive-2-expected_context0 + ``expect`` extracts the value from an ``Ok``. .. code-block:: python @@ -1340,7 +1340,7 @@ def expect(self, msg: str) -> Never: def expect_err(self, msg: str) -> E: # noqa: ARG002 """Papertrail examples: - monad0-must be err-2-expected_context0 + ``expect_err`` extracts the error from an ``Err``. .. code-block:: python @@ -1353,28 +1353,28 @@ def expect_err(self, msg: str) -> E: # noqa: ARG002 def flatten(self) -> Result[Never, E]: """Papertrail examples: - monad0-expected_result0 + If you call ``flatten`` on three nested ``Ok`` values then two nested ``Ok`` values are returned. Only the outer monad is removed .. code-block:: python >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True - monad1-expected_result1 + This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad. .. code-block:: python >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True - monad2-expected_result2 + Calling ``flatten`` if the inner is not a monad of the same type is a no-op. .. code-block:: python >>> Ok(inner=2).flatten() == Ok(inner=2) True - monad3-expected_result3 + An ``Err`` passes through ``flatten`` unchanged. .. code-block:: python @@ -1387,14 +1387,14 @@ def flatten(self) -> Result[Never, E]: def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG002 """Papertrail examples: - monad0-append_to_list-expected_result0 + If you call ``inspect`` on an ``Ok`` then the original ``Ok`` is returned after the function is called, this is used for logging or side outputs that shouldn't disrupt the existing flow. .. code-block:: python >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - monad1-append_to_list-expected_result1 + If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called. .. code-block:: python @@ -1407,14 +1407,14 @@ def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: """Papertrail examples: - monad0-append_to_list-expected_result0 + If you call ``inspect_err`` on an ``Err`` then the original ``Err`` is returned after the function is called, it's essentially the inverse of ``inspect``. .. code-block:: python >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True - monad1-append_to_list-expected_result1 + Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called. .. code-block:: python @@ -1428,14 +1428,14 @@ def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: def is_err(self) -> bool: """Papertrail examples: - monad0-False + ``is_err`` reports ``False`` for an ``Ok``. .. code-block:: python >>> Ok(inner=2).is_err() == False True - monad1-True + For an ``Err``, ``is_err`` reports ``True``. .. code-block:: python @@ -1448,21 +1448,21 @@ def is_err(self) -> bool: def is_err_and(self, fn: Callable[[E], bool]) -> bool: """Papertrail examples: - monad0-is_even-False + ``is_err_and`` requires both the monad to be an ``Err`` and the returned value of the passed in callable to be ``True``, for example, if you called ``is_err_and`` with a function that checks for evenness on an ``Err(1)`` then the result is ``False``. .. code-block:: python >>> Err(error=1).is_err_and(is_even) == False True - monad1-is_even-True + Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned. .. code-block:: python >>> Err(error=2).is_err_and(is_even) == True True - monad2-is_even-False + Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called. .. code-block:: python @@ -1475,14 +1475,14 @@ def is_err_and(self, fn: Callable[[E], bool]) -> bool: def is_ok(self) -> bool: """Papertrail examples: - monad0-True + ``is_ok`` reports ``True`` for an ``Ok``. .. code-block:: python >>> Ok(inner=2).is_ok() == True True - monad1-False + For an ``Err``, ``is_ok`` reports ``False``. .. code-block:: python @@ -1495,21 +1495,21 @@ def is_ok(self) -> bool: def is_ok_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 """Papertrail examples: - monad0-is_even-False + When the predicate rejects an ``Ok``, ``is_ok_and`` reports ``False``. .. code-block:: python >>> Ok(inner=1).is_ok_and(is_even) == False True - monad1-is_even-True + When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``. .. code-block:: python >>> Ok(inner=2).is_ok_and(is_even) == True True - monad2-is_even-False + An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -1527,14 +1527,14 @@ def map[U, **P]( ) -> Result[U, E]: """Papertrail examples: - monad0-add_one-expected_result0 + Mapping an ``Ok`` applies the function and wraps its result in a new ``Ok``. .. code-block:: python >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - monad1-add_one-expected_result1 + Mapping an ``Err`` leaves it unchanged and skips the function. .. code-block:: python @@ -1549,14 +1549,14 @@ def map_err[F, **P]( ) -> Result[Never, F]: """Papertrail examples: - monad0-add_one-expected_result0 + Mapping an ``Err`` applies the function and wraps its result in a new ``Err``. .. code-block:: python >>> Err(error=1).map_err(add_one) == Err(error=2) True - monad1-add_one-expected_result1 + Mapping an ``Ok`` leaves it unchanged and skips the function. .. code-block:: python @@ -1577,14 +1577,14 @@ def map_or[U, **P]( ) -> U: """Papertrail examples: - monad0-42-len-3 + For an ``Ok``, ``map_or`` uses the function result instead of the default. .. code-block:: python >>> Ok(inner="foo").map_or(42, len) == 3 True - monad1-42-len-42 + For an ``Err``, ``map_or`` returns the supplied default. .. code-block:: python @@ -1603,14 +1603,14 @@ def map_or_else[U, **P]( ) -> U: """Papertrail examples: - monad0-get_42-len-3 + An ``Ok`` makes ``map_or_else`` use the mapping function. .. code-block:: python >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True - monad1-get_42-len-42 + An ``Err`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -1623,14 +1623,14 @@ def map_or_else[U, **P]( def ok(self) -> Option[Never]: """Papertrail examples: - monad0-expected_result0 + An ``Ok`` converts to ``Some`` through ``ok``. .. code-block:: python >>> Ok(inner=2).ok() == Some(inner=2) True - monad1-expected_result1 + An ``Err`` converts to ``Null`` through ``ok``. .. code-block:: python @@ -1645,28 +1645,28 @@ def ok(self) -> Option[Never]: def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: """Papertrail examples: - monad0-res0-expected_result0 + An ``Ok`` keeps its value when ``or_`` receives an ``Err``. .. code-block:: python >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - monad1-res1-expected_result1 + An ``Err`` gives way to an ``Ok`` passed to ``or_``. .. code-block:: python >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True - monad2-res2-expected_result2 + When both values are ``Ok``, ``or_`` keeps the first one. .. code-block:: python >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - monad3-res3-expected_result3 + When both values are ``Err``, ``or_`` keeps the first error. .. code-block:: python @@ -1681,21 +1681,21 @@ def or_else[F, **P]( ) -> Result[Never, F]: """Papertrail examples: - monad0-get_ok_vikings-expected_result0 + An ``Ok`` passes through ``or_else`` without calling the function. .. code-block:: python >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - monad1-get_ok_vikings-expected_result1 + For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - monad2-Err-expected_result2 + If the fallback also produces an ``Err``, ``or_else`` keeps the original error. .. code-block:: python @@ -1708,21 +1708,21 @@ def or_else[F, **P]( def transpose(self) -> Option[Result[Never, E]]: """Papertrail examples: - monad0-expected_result0-expected_context0 + Transposing ``Ok(Some(value))`` produces ``Some(Ok(value))``. .. code-block:: python >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True - monad1-expected_result1-expected_context1 + Transposing ``Ok(Null())`` produces ``Null``. .. code-block:: python >>> Ok(inner=Null()).transpose() == Null() True - monad2-expected_result2-expected_context2 + Transposing an ``Err`` wraps it in ``Some``. .. code-block:: python @@ -1737,7 +1737,7 @@ def transpose(self) -> Option[Result[Never, E]]: def unwrap(self) -> Never: """Papertrail examples: - monad0-2-expected_context0 + ``unwrap`` extracts the value from an ``Ok``. .. code-block:: python @@ -1752,7 +1752,7 @@ def unwrap(self) -> Never: def unwrap_err(self) -> E: """Papertrail examples: - monad0-failed-expected_context0 + ``unwrap_err`` extracts the error from an ``Err``. .. code-block:: python @@ -1765,14 +1765,14 @@ def unwrap_err(self) -> E: def unwrap_or[U](self, default: U) -> U: """Papertrail examples: - monad0-bike-car + With an ``Ok``, ``unwrap_or`` returns the value and ignores the default. .. code-block:: python >>> Ok(inner="car").unwrap_or("bike") == "car" True - monad1-bike-bike + With an ``Err``, ``unwrap_or`` returns the default. .. code-block:: python @@ -1785,14 +1785,14 @@ def unwrap_or[U](self, default: U) -> U: def unwrap_or_else[U](self, fn: Callable[[E], U]) -> U: """Papertrail examples: - monad0-get_42-4 + An ``Ok`` makes ``unwrap_or_else`` return its value without calling the function. .. code-block:: python >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - monad1-get_42-42 + An ``Err`` makes ``unwrap_or_else`` return the function result. .. code-block:: python diff --git a/tests/monads/test_result_v2.py b/tests/monads/test_result_v2.py index 9cc1027..4492809 100644 --- a/tests/monads/test_result_v2.py +++ b/tests/monads/test_result_v2.py @@ -16,19 +16,19 @@ Ok(2), Err("late error"), Err("late error"), - id="Calling ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``.", + id="If you call ``and_`` on an ``Ok`` with an ``Err`` as the arg then the ``Err`` takes precedence over the ``Ok``.", ), pytest.param( Err("early error"), Ok("foo"), Err("early error"), - id="Calling ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``.", + id="If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``.", ), pytest.param( Err("not a 2"), Err("late error"), Err("not a 2"), - id="Calling ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded.", + id="If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded.", ), pytest.param( Ok(2), @@ -49,9 +49,24 @@ def must_be_less_than_10(x: int) -> Result[int, str]: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Ok(2), must_be_less_than_10, Ok(2)), - pytest.param(Ok(20), must_be_less_than_10, Err("too high")), - pytest.param(Err("not a number"), must_be_less_than_10, Err("not a number")), + pytest.param( + Ok(2), + must_be_less_than_10, + Ok(2), + id="An ``Ok`` passed to ``and_then`` returns the ``Ok`` produced by the function.", + ), + pytest.param( + Ok(20), + must_be_less_than_10, + Err("too high"), + id="When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result.", + ), + pytest.param( + Err("not a number"), + must_be_less_than_10, + Err("not a number"), + id="An existing ``Err`` passes through ``and_then`` unchanged, without calling the function.", + ), ], ) def test_and_then(request, monad: Result, fn, expected_result) -> None: @@ -59,7 +74,15 @@ def test_and_then(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Ok(2), Ok(2)), pytest.param(Err(2), Err(2))] + ("monad", "expected_result"), + [ + pytest.param( + Ok(2), Ok(2), id="Cloning an ``Ok`` creates a separate ``Ok`` with the same value." + ), + pytest.param( + Err(2), Err(2), id="Cloning an ``Err`` creates a separate ``Err`` with the same value." + ), + ], ) def test_cloned(request, monad: Result, expected_result) -> None: assert example(monad.cloned, description=request.node.callspec.id) == expected_result @@ -68,7 +91,14 @@ def test_cloned(request, monad: Result, expected_result) -> None: @pytest.mark.parametrize( ("monad", "expected_result"), - [pytest.param(Ok(2), Null()), pytest.param(Err("Nothing here"), Some("Nothing here"))], + [ + pytest.param(Ok(2), Null(), id="An ``Ok`` has no error, so ``err`` returns ``Null``."), + pytest.param( + Err("Nothing here"), + Some("Nothing here"), + id="If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned.", + ), + ], ) def test_err(request, monad: Result, expected_result) -> None: assert example(monad.err, description=request.node.callspec.id) == expected_result @@ -77,8 +107,20 @@ def test_err(request, monad: Result, expected_result) -> None: @pytest.mark.parametrize( ("monad", "msg", "expected_result", "expected_context"), [ - pytest.param(Ok(2), "must be positive", 2, nullcontext()), - pytest.param(Err(), "must be positive", None, pytest.raises(ValueError)), + pytest.param( + Ok(2), + "must be positive", + 2, + nullcontext(), + id="``expect`` extracts the value from an ``Ok``.", + ), + pytest.param( + Err(), + "must be positive", + None, + pytest.raises(ValueError), + id="``expect`` raises ``ValueError`` for an ``Err`` and uses the supplied message.", + ), ], ) def test_expect(request, monad: Result, msg, expected_result, expected_context) -> None: @@ -89,8 +131,20 @@ def test_expect(request, monad: Result, msg, expected_result, expected_context) @pytest.mark.parametrize( ("monad", "msg", "expected_result", "expected_context"), [ - pytest.param(Err(2), "must be err", 2, nullcontext()), - pytest.param(Ok(), "must be err", None, pytest.raises(ValueError)), + pytest.param( + Err(2), + "must be err", + 2, + nullcontext(), + id="``expect_err`` extracts the error from an ``Err``.", + ), + pytest.param( + Ok(), + "must be err", + None, + pytest.raises(ValueError), + id="``expect_err`` raises ``ValueError`` for an ``Ok`` and uses the supplied message.", + ), ], ) def test_expect_err(request, monad: Result, msg, expected_result, expected_context) -> None: @@ -103,10 +157,22 @@ def test_expect_err(request, monad: Result, msg, expected_result, expected_conte @pytest.mark.parametrize( ("monad", "expected_result"), [ - pytest.param(Ok(Ok(Ok(2))), Ok(Ok(2))), - pytest.param(Ok(Ok(2)), Ok(2)), - pytest.param(Ok(2), Ok(2)), - pytest.param(Err(), Err()), + pytest.param( + Ok(Ok(Ok(2))), + Ok(Ok(2)), + id="If you call ``flatten`` on three nested ``Ok`` values then two nested ``Ok`` values are returned. Only the outer monad is removed", + ), + pytest.param( + Ok(Ok(2)), + Ok(2), + id="This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad.", + ), + pytest.param( + Ok(2), + Ok(2), + id="Calling ``flatten`` if the inner is not a monad of the same type is a no-op.", + ), + pytest.param(Err(), Err(), id="An ``Err`` passes through ``flatten`` unchanged."), ], ) def test_flatten(request, monad: Result, expected_result) -> None: @@ -120,8 +186,18 @@ def append_to_list(x) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Ok([1]), append_to_list, Ok([1])), - pytest.param(Err("un-appendable"), append_to_list, Err("un-appendable")), + pytest.param( + Ok([1]), + append_to_list, + Ok([1]), + id="If you call ``inspect`` on an ``Ok`` then the original ``Ok`` is returned after the function is called, this is used for logging or side outputs that shouldn't disrupt the existing flow.", + ), + pytest.param( + Err("un-appendable"), + append_to_list, + Err("un-appendable"), + id="If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called.", + ), ], ) def test_inspect(request, monad: Result, fn, expected_result) -> None: @@ -131,8 +207,18 @@ def test_inspect(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Err([1]), append_to_list, Err([1])), - pytest.param(Ok("un-appendable"), append_to_list, Ok("un-appendable")), + pytest.param( + Err([1]), + append_to_list, + Err([1]), + id="If you call ``inspect_err`` on an ``Err`` then the original ``Err`` is returned after the function is called, it's essentially the inverse of ``inspect``.", + ), + pytest.param( + Ok("un-appendable"), + append_to_list, + Ok("un-appendable"), + id="Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called.", + ), ], ) def test_inspect_err(request, monad: Result, fn, expected_result) -> None: @@ -140,7 +226,11 @@ def test_inspect_err(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Ok(2), False), pytest.param(Err(2), True)] + ("monad", "expected_result"), + [ + pytest.param(Ok(2), False, id="``is_err`` reports ``False`` for an ``Ok``."), + pytest.param(Err(2), True, id="For an ``Err``, ``is_err`` reports ``True``."), + ], ) def test_is_err(request, monad: Result, expected_result) -> None: assert example(monad.is_err, description=request.node.callspec.id) == expected_result @@ -149,9 +239,24 @@ def test_is_err(request, monad: Result, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Err(1), is_even, False), - pytest.param(Err(2), is_even, True), - pytest.param(Ok(2), is_even, False), + pytest.param( + Err(1), + is_even, + False, + id="``is_err_and`` requires both the monad to be an ``Err`` and the returned value of the passed in callable to be ``True``, for example, if you called ``is_err_and`` with a function that checks for evenness on an ``Err(1)`` then the result is ``False``.", + ), + pytest.param( + Err(2), + is_even, + True, + id="Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned.", + ), + pytest.param( + Ok(2), + is_even, + False, + id="Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called.", + ), ], ) def test_is_err_and(request, monad: Result, fn, expected_result) -> None: @@ -159,7 +264,11 @@ def test_is_err_and(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Ok(2), True), pytest.param(Err(2), False)] + ("monad", "expected_result"), + [ + pytest.param(Ok(2), True, id="``is_ok`` reports ``True`` for an ``Ok``."), + pytest.param(Err(2), False, id="For an ``Err``, ``is_ok`` reports ``False``."), + ], ) def test_is_ok(request, monad: Result, expected_result) -> None: assert example(monad.is_ok, description=request.node.callspec.id) == expected_result @@ -168,9 +277,24 @@ def test_is_ok(request, monad: Result, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Ok(1), is_even, False), - pytest.param(Ok(2), is_even, True), - pytest.param(Err(2), is_even, False), + pytest.param( + Ok(1), + is_even, + False, + id="When the predicate rejects an ``Ok``, ``is_ok_and`` reports ``False``.", + ), + pytest.param( + Ok(2), + is_even, + True, + id="When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``.", + ), + pytest.param( + Err(2), + is_even, + False, + id="An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate.", + ), ], ) def test_is_ok_and(request, monad: Result, fn, expected_result) -> None: @@ -179,7 +303,20 @@ def test_is_ok_and(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Ok(1), add_one, Ok(2)), pytest.param(Err(1), add_one, Err(1))], + [ + pytest.param( + Ok(1), + add_one, + Ok(2), + id="Mapping an ``Ok`` applies the function and wraps its result in a new ``Ok``.", + ), + pytest.param( + Err(1), + add_one, + Err(1), + id="Mapping an ``Err`` leaves it unchanged and skips the function.", + ), + ], ) def test_map(request, monad: Result, fn, expected_result) -> None: assert example(monad.map, fn, description=request.node.callspec.id) == expected_result @@ -187,7 +324,20 @@ def test_map(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Err(1), add_one, Err(2)), pytest.param(Ok(1), add_one, Ok(1))], + [ + pytest.param( + Err(1), + add_one, + Err(2), + id="Mapping an ``Err`` applies the function and wraps its result in a new ``Err``.", + ), + pytest.param( + Ok(1), + add_one, + Ok(1), + id="Mapping an ``Ok`` leaves it unchanged and skips the function.", + ), + ], ) def test_map_err(request, monad: Result, fn, expected_result) -> None: assert example(monad.map_err, fn, description=request.node.callspec.id) == expected_result @@ -195,7 +345,18 @@ def test_map_err(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), - [pytest.param(Ok("foo"), 42, len, 3), pytest.param(Err(), 42, len, 42)], + [ + pytest.param( + Ok("foo"), + 42, + len, + 3, + id="For an ``Ok``, ``map_or`` uses the function result instead of the default.", + ), + pytest.param( + Err(), 42, len, 42, id="For an ``Err``, ``map_or`` returns the supplied default." + ), + ], ) def test_map_or(request, monad: Result, default, fn, expected_result) -> None: assert ( @@ -205,7 +366,18 @@ def test_map_or(request, monad: Result, default, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), - [pytest.param(Ok("foo"), get_42, len, 3), pytest.param(Err(), get_42, len, 42)], + [ + pytest.param( + Ok("foo"), + get_42, + len, + 3, + id="An ``Ok`` makes ``map_or_else`` use the mapping function.", + ), + pytest.param( + Err(), get_42, len, 42, id="An ``Err`` makes ``map_or_else`` use the default function." + ), + ], ) def test_map_or_else(request, monad: Result, default, fn, expected_result) -> None: assert ( @@ -215,7 +387,11 @@ def test_map_or_else(request, monad: Result, default, fn, expected_result) -> No @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Ok(2), Some(2)), pytest.param(Err(2), Null())] + ("monad", "expected_result"), + [ + pytest.param(Ok(2), Some(2), id="An ``Ok`` converts to ``Some`` through ``ok``."), + pytest.param(Err(2), Null(), id="An ``Err`` converts to ``Null`` through ``ok``."), + ], ) def test_ok(request, monad: Result, expected_result) -> None: assert example(monad.ok, description=request.node.callspec.id) == expected_result @@ -224,10 +400,24 @@ def test_ok(request, monad: Result, expected_result) -> None: @pytest.mark.parametrize( ("monad", "res", "expected_result"), [ - pytest.param(Ok(2), Err("foo"), Ok(2)), - pytest.param(Err("foo"), Ok(100), Ok(100)), - pytest.param(Ok(2), Ok(100), Ok(2)), - pytest.param(Err("foo"), Err("foo"), Err("foo")), + pytest.param( + Ok(2), + Err("foo"), + Ok(2), + id="An ``Ok`` keeps its value when ``or_`` receives an ``Err``.", + ), + pytest.param( + Err("foo"), Ok(100), Ok(100), id="An ``Err`` gives way to an ``Ok`` passed to ``or_``." + ), + pytest.param( + Ok(2), Ok(100), Ok(2), id="When both values are ``Ok``, ``or_`` keeps the first one." + ), + pytest.param( + Err("foo"), + Err("foo"), + Err("foo"), + id="When both values are ``Err``, ``or_`` keeps the first error.", + ), ], ) def test_or_(request, monad: Result, res, expected_result) -> None: @@ -237,9 +427,24 @@ def test_or_(request, monad: Result, res, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Ok("barbarians"), get_ok_vikings, Ok("barbarians")), - pytest.param(Err("foo"), get_ok_vikings, Ok("vikings")), - pytest.param(Err("foo"), Err, Err("foo")), + pytest.param( + Ok("barbarians"), + get_ok_vikings, + Ok("barbarians"), + id="An ``Ok`` passes through ``or_else`` without calling the function.", + ), + pytest.param( + Err("foo"), + get_ok_vikings, + Ok("vikings"), + id="For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function.", + ), + pytest.param( + Err("foo"), + Err, + Err("foo"), + id="If the fallback also produces an ``Err``, ``or_else`` keeps the original error.", + ), ], ) def test_or_else(request, monad: Result, fn, expected_result) -> None: @@ -249,10 +454,24 @@ def test_or_else(request, monad: Result, fn, expected_result) -> None: @pytest.mark.parametrize( ("monad", "expected_result", "expected_context"), [ - pytest.param(Ok(Some(5)), Some(Ok(5)), nullcontext()), - pytest.param(Ok(Null()), Null(), nullcontext()), - pytest.param(Err(), Some(Err()), nullcontext()), - pytest.param(Ok(2), None, pytest.raises(TypeError)), + pytest.param( + Ok(Some(5)), + Some(Ok(5)), + nullcontext(), + id="Transposing ``Ok(Some(value))`` produces ``Some(Ok(value))``.", + ), + pytest.param( + Ok(Null()), Null(), nullcontext(), id="Transposing ``Ok(Null())`` produces ``Null``." + ), + pytest.param( + Err(), Some(Err()), nullcontext(), id="Transposing an ``Err`` wraps it in ``Some``." + ), + pytest.param( + Ok(2), + None, + pytest.raises(TypeError), + id="Transposing an ``Ok`` with an unsupported value raises ``TypeError``.", + ), ], ) def test_transpose(request, monad: Result, expected_result, expected_context) -> None: @@ -262,7 +481,15 @@ def test_transpose(request, monad: Result, expected_result, expected_context) -> @pytest.mark.parametrize( ("monad", "expected_result", "expected_context"), - [pytest.param(Ok(2), 2, nullcontext()), pytest.param(Err(), None, pytest.raises(TypeError))], + [ + pytest.param(Ok(2), 2, nullcontext(), id="``unwrap`` extracts the value from an ``Ok``."), + pytest.param( + Err(), + None, + pytest.raises(TypeError), + id="``unwrap`` raises ``TypeError`` when the result is an ``Err``.", + ), + ], ) def test_unwrap(request, monad: Result, expected_result, expected_context) -> None: with expected_context: @@ -272,8 +499,18 @@ def test_unwrap(request, monad: Result, expected_result, expected_context) -> No @pytest.mark.parametrize( ("monad", "expected_result", "expected_context"), [ - pytest.param(Err("failed"), "failed", nullcontext()), - pytest.param(Ok(2), None, pytest.raises(TypeError)), + pytest.param( + Err("failed"), + "failed", + nullcontext(), + id="``unwrap_err`` extracts the error from an ``Err``.", + ), + pytest.param( + Ok(2), + None, + pytest.raises(TypeError), + id="``unwrap_err`` raises ``TypeError`` when the result is an ``Ok``.", + ), ], ) def test_unwrap_err(request, monad: Result, expected_result, expected_context) -> None: @@ -283,7 +520,17 @@ def test_unwrap_err(request, monad: Result, expected_result, expected_context) - @pytest.mark.parametrize( ("monad", "default", "expected_result"), - [pytest.param(Ok("car"), "bike", "car"), pytest.param(Err(), "bike", "bike")], + [ + pytest.param( + Ok("car"), + "bike", + "car", + id="With an ``Ok``, ``unwrap_or`` returns the value and ignores the default.", + ), + pytest.param( + Err(), "bike", "bike", id="With an ``Err``, ``unwrap_or`` returns the default." + ), + ], ) def test_unwrap_or(request, monad: Result, default, expected_result) -> None: assert ( @@ -293,7 +540,17 @@ def test_unwrap_or(request, monad: Result, default, expected_result) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Ok(4), get_42, 4), pytest.param(Err(), get_42, 42)], + [ + pytest.param( + Ok(4), + get_42, + 4, + id="An ``Ok`` makes ``unwrap_or_else`` return its value without calling the function.", + ), + pytest.param( + Err(), get_42, 42, id="An ``Err`` makes ``unwrap_or_else`` return the function result." + ), + ], ) def test_unwrap_or_else(request, monad: Result, fn, expected_result) -> None: assert ( From 9949cbb20face81bf48361e1d7c262ff7e5df675 Mon Sep 17 00:00:00 2001 From: ed cuss Date: Sat, 26 Sep 2026 20:14:44 +0100 Subject: [PATCH 10/11] docs: remove extra newline --- coverage.svg | 2 +- .../transformation/format_examples.py | 2 +- src/danom/_monads/_either.py | 12 -- src/danom/_monads/_option.py | 115 ------------------ src/danom/_monads/_result_v2.py | 94 -------------- 5 files changed, 2 insertions(+), 223 deletions(-) diff --git a/coverage.svg b/coverage.svg index cc997ea..ae5b368 100644 --- a/coverage.svg +++ b/coverage.svg @@ -13,4 +13,4 @@ coverage 100.00% - + \ No newline at end of file diff --git a/dev_tools/create_examples/transformation/format_examples.py b/dev_tools/create_examples/transformation/format_examples.py index cbe1737..d8e479f 100644 --- a/dev_tools/create_examples/transformation/format_examples.py +++ b/dev_tools/create_examples/transformation/format_examples.py @@ -45,6 +45,6 @@ def reduce_examples_to_example_str( fn_examples: dict[str, dict[str, list[str]]], ) -> dict[str, dict[str, str]]: return { - path: {k: "Papertrail examples:\n\n" + "\n\n".join(v) + "\n::" for k, v in fn.items()} + path: {k: "Papertrail examples:\n\n" + "\n".join(v) + "\n::" for k, v in fn.items()} for path, fn in fn_examples.items() } diff --git a/src/danom/_monads/_either.py b/src/danom/_monads/_either.py index a281ac2..e8d6572 100644 --- a/src/danom/_monads/_either.py +++ b/src/danom/_monads/_either.py @@ -50,7 +50,6 @@ def is_ok(self) -> bool: True - .. code-block:: python >>> Left(inner=None).is_ok() == False @@ -75,7 +74,6 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Either[U_co, True - .. code-block:: python >>> Left(inner=None).map(add_one) == Left(inner=None) @@ -100,7 +98,6 @@ def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Either[U True - .. code-block:: python >>> Left(inner=0).map_err(add_one) == Left(inner=1) @@ -149,21 +146,18 @@ def flatten(self) -> Either[T_co, E_co]: True - .. code-block:: python >>> Right(inner=Left(inner=None)).flatten() == Left(inner=None) True - .. code-block:: python >>> Left(inner=Right(inner=None)).flatten() == Right(inner=None) True - .. code-block:: python >>> Left(inner=Left(inner=None)).flatten() == Left(inner=None) @@ -191,7 +185,6 @@ def is_ok(self) -> Literal[True]: True - .. code-block:: python >>> Left(inner=None).is_ok() == False @@ -211,7 +204,6 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Right[U_co]: True - .. code-block:: python >>> Left(inner=None).map(add_one) == Left(inner=None) @@ -231,7 +223,6 @@ def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # True - .. code-block:: python >>> Left(inner=0).map_err(add_one) == Left(inner=1) @@ -263,7 +254,6 @@ def is_ok(self) -> Literal[False]: True - .. code-block:: python >>> Left(inner=None).is_ok() == False @@ -283,7 +273,6 @@ def map(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Self: # noq True - .. code-block:: python >>> Left(inner=None).map(add_one) == Left(inner=None) @@ -303,7 +292,6 @@ def map_err(self, func: Mappable, *args: P.args, **kwargs: P.kwargs) -> Left[F_c True - .. code-block:: python >>> Left(inner=0).map_err(add_one) == Left(inner=1) diff --git a/src/danom/_monads/_option.py b/src/danom/_monads/_option.py index a9bc62e..3bf4852 100644 --- a/src/danom/_monads/_option.py +++ b/src/danom/_monads/_option.py @@ -25,21 +25,18 @@ def and_(self, opt_b: Option[T]) -> Option[T]: True - .. code-block:: python >>> Null().and_(Some(inner="foo")) == Null() True - .. code-block:: python >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True - .. code-block:: python >>> Null().and_(Null()) == Null() @@ -60,14 +57,12 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: True - .. code-block:: python >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True - .. code-block:: python >>> Null().and_then(must_be_less_than_10) == Null() @@ -88,7 +83,6 @@ def as_list(self) -> list[T]: True - .. code-block:: python >>> Null().as_list() == [] @@ -109,7 +103,6 @@ def as_tuple(self) -> tuple[T, ...]: True - .. code-block:: python >>> Null().as_tuple() == () @@ -129,7 +122,6 @@ def cloned(self) -> Option[T]: True - .. code-block:: python >>> Null().cloned() == Null() @@ -164,14 +156,12 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: True - .. code-block:: python >>> Some(inner=4).filter_(is_even) == Some(inner=4) True - .. code-block:: python >>> Null().filter_(is_even) == Null() @@ -192,21 +182,18 @@ def flatten(self) -> Option[T]: True - .. code-block:: python >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True - .. code-block:: python >>> Some(inner=2).flatten() == Some(inner=2) True - .. code-block:: python >>> Null().flatten() == Null() @@ -227,7 +214,6 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: True - .. code-block:: python >>> Null().inspect(append_to_list) == Null() @@ -248,7 +234,6 @@ def is_none(self) -> bool: True - .. code-block:: python >>> Null().is_none() == True @@ -269,14 +254,12 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: True - .. code-block:: python >>> Some(inner=2).is_none_or(is_even) == True True - .. code-block:: python >>> Null().is_none_or(is_even) == True @@ -297,7 +280,6 @@ def is_some(self) -> bool: True - .. code-block:: python >>> Null().is_some() == False @@ -318,14 +300,12 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: True - .. code-block:: python >>> Some(inner=2).is_some_and(is_even) == True True - .. code-block:: python >>> Null().is_some_and(is_even) == False @@ -346,7 +326,6 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: True - .. code-block:: python >>> Null().map(add_one) == Null() @@ -367,7 +346,6 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: True - .. code-block:: python >>> Null().map_or(42, len) == 42 @@ -388,7 +366,6 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: True - .. code-block:: python >>> Null().map_or_else(get_42, len) == 42 @@ -409,7 +386,6 @@ def ok_or[E](self, err: E) -> Result[T, E]: True - .. code-block:: python >>> Null().ok_or(0) == Err(error=0) @@ -430,7 +406,6 @@ def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: True - .. code-block:: python >>> Null().ok_or_else(get_42) == Err(error=42) @@ -451,21 +426,18 @@ def or_(self, opt_b: Option[T]) -> Option[T]: True - .. code-block:: python >>> Null().or_(Some(inner=100)) == Some(inner=100) True - .. code-block:: python >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True - .. code-block:: python >>> Null().or_(Null()) == Null() @@ -486,14 +458,12 @@ def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: True - .. code-block:: python >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True - .. code-block:: python >>> Null().or_else(Null) == Null() @@ -514,7 +484,6 @@ def replace(self, value: T) -> Option[T]: True - .. code-block:: python >>> Null().replace(3) == Null() @@ -535,14 +504,12 @@ def transpose[E](self) -> Result[Option[T], E]: True - .. code-block:: python >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True - .. code-block:: python >>> Null().transpose() == Ok(inner=Null()) @@ -577,7 +544,6 @@ def unwrap_or(self, default: T) -> T: True - .. code-block:: python >>> Null().unwrap_or("bike") == "bike" @@ -598,7 +564,6 @@ def unwrap_or_else(self, fn: Callable[..., T]) -> T: True - .. code-block:: python >>> Null().unwrap_or_else(get_42) == 42 @@ -619,14 +584,12 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: True - .. code-block:: python >>> Some(inner=1).zip(Null()) == Null() True - .. code-block:: python >>> Null().zip(Some(inner=1)) == Null() @@ -647,14 +610,12 @@ def unzip[U](self) -> tuple[Option[T], Option[U]]: True - .. code-block:: python >>> Some(inner=4).unzip() == (Null(), Null()) True - .. code-block:: python >>> Null().unzip() == (Null(), Null()) @@ -679,21 +640,18 @@ def and_(self, opt_b: Option[T]) -> Option[T]: True - .. code-block:: python >>> Null().and_(Some(inner="foo")) == Null() True - .. code-block:: python >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True - .. code-block:: python >>> Null().and_(Null()) == Null() @@ -713,14 +671,12 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: True - .. code-block:: python >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True - .. code-block:: python >>> Null().and_then(must_be_less_than_10) == Null() @@ -740,7 +696,6 @@ def as_list(self) -> list[T]: True - .. code-block:: python >>> Null().as_list() == [] @@ -760,7 +715,6 @@ def as_tuple(self) -> tuple[T, ...]: True - .. code-block:: python >>> Null().as_tuple() == () @@ -793,14 +747,12 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: True - .. code-block:: python >>> Some(inner=4).filter_(is_even) == Some(inner=4) True - .. code-block:: python >>> Null().filter_(is_even) == Null() @@ -820,21 +772,18 @@ def flatten(self) -> Option[T]: True - .. code-block:: python >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True - .. code-block:: python >>> Some(inner=2).flatten() == Some(inner=2) True - .. code-block:: python >>> Null().flatten() == Null() @@ -856,7 +805,6 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: True - .. code-block:: python >>> Null().inspect(append_to_list) == Null() @@ -877,7 +825,6 @@ def is_none(self) -> bool: True - .. code-block:: python >>> Null().is_none() == True @@ -897,14 +844,12 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: True - .. code-block:: python >>> Some(inner=2).is_none_or(is_even) == True True - .. code-block:: python >>> Null().is_none_or(is_even) == True @@ -924,7 +869,6 @@ def is_some(self) -> bool: True - .. code-block:: python >>> Null().is_some() == False @@ -944,14 +888,12 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: True - .. code-block:: python >>> Some(inner=2).is_some_and(is_even) == True True - .. code-block:: python >>> Null().is_some_and(is_even) == False @@ -971,7 +913,6 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: True - .. code-block:: python >>> Null().map(add_one) == Null() @@ -991,7 +932,6 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 True - .. code-block:: python >>> Null().map_or(42, len) == 42 @@ -1011,7 +951,6 @@ def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: True - .. code-block:: python >>> Null().map_or_else(get_42, len) == 42 @@ -1031,7 +970,6 @@ def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 True - .. code-block:: python >>> Null().ok_or(0) == Err(error=0) @@ -1053,7 +991,6 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 True - .. code-block:: python >>> Null().ok_or_else(get_42) == Err(error=42) @@ -1075,21 +1012,18 @@ def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 True - .. code-block:: python >>> Null().or_(Some(inner=100)) == Some(inner=100) True - .. code-block:: python >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True - .. code-block:: python >>> Null().or_(Null()) == Null() @@ -1109,14 +1043,12 @@ def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: # noqa: ARG002 True - .. code-block:: python >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True - .. code-block:: python >>> Null().or_else(Null) == Null() @@ -1136,7 +1068,6 @@ def replace(self, value: T) -> Option[T]: True - .. code-block:: python >>> Null().replace(3) == Null() @@ -1156,14 +1087,12 @@ def transpose(self) -> Result[Option[T], Option[T]]: True - .. code-block:: python >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True - .. code-block:: python >>> Null().transpose() == Ok(inner=Null()) @@ -1202,7 +1131,6 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 True - .. code-block:: python >>> Null().unwrap_or("bike") == "bike" @@ -1222,7 +1150,6 @@ def unwrap_or_else(self, fn: Callable[[], T]) -> T: # noqa: ARG002 True - .. code-block:: python >>> Null().unwrap_or_else(get_42) == 42 @@ -1242,14 +1169,12 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: True - .. code-block:: python >>> Some(inner=1).zip(Null()) == Null() True - .. code-block:: python >>> Null().zip(Some(inner=1)) == Null() @@ -1271,14 +1196,12 @@ def unzip[U](self) -> tuple[Option[T], Option[U]]: True - .. code-block:: python >>> Some(inner=4).unzip() == (Null(), Null()) True - .. code-block:: python >>> Null().unzip() == (Null(), Null()) @@ -1303,21 +1226,18 @@ def and_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 True - .. code-block:: python >>> Null().and_(Some(inner="foo")) == Null() True - .. code-block:: python >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True - .. code-block:: python >>> Null().and_(Null()) == Null() @@ -1337,14 +1257,12 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: # noqa: ARG00 True - .. code-block:: python >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True - .. code-block:: python >>> Null().and_then(must_be_less_than_10) == Null() @@ -1364,7 +1282,6 @@ def as_list(self) -> list[T]: True - .. code-block:: python >>> Null().as_list() == [] @@ -1384,7 +1301,6 @@ def as_tuple(self) -> tuple[T, ...]: True - .. code-block:: python >>> Null().as_tuple() == () @@ -1417,14 +1333,12 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: # noqa: ARG002 True - .. code-block:: python >>> Some(inner=4).filter_(is_even) == Some(inner=4) True - .. code-block:: python >>> Null().filter_(is_even) == Null() @@ -1444,21 +1358,18 @@ def flatten(self) -> Option[T]: True - .. code-block:: python >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True - .. code-block:: python >>> Some(inner=2).flatten() == Some(inner=2) True - .. code-block:: python >>> Null().flatten() == Null() @@ -1478,7 +1389,6 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: # noqa: ARG002 True - .. code-block:: python >>> Null().inspect(append_to_list) == Null() @@ -1498,7 +1408,6 @@ def is_none(self) -> bool: True - .. code-block:: python >>> Null().is_none() == True @@ -1518,14 +1427,12 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 True - .. code-block:: python >>> Some(inner=2).is_none_or(is_even) == True True - .. code-block:: python >>> Null().is_none_or(is_even) == True @@ -1545,7 +1452,6 @@ def is_some(self) -> bool: True - .. code-block:: python >>> Null().is_some() == False @@ -1565,14 +1471,12 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 True - .. code-block:: python >>> Some(inner=2).is_some_and(is_even) == True True - .. code-block:: python >>> Null().is_some_and(is_even) == False @@ -1592,7 +1496,6 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 True - .. code-block:: python >>> Null().map(add_one) == Null() @@ -1612,7 +1515,6 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 True - .. code-block:: python >>> Null().map_or(42, len) == 42 @@ -1632,7 +1534,6 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: True - .. code-block:: python >>> Null().map_or_else(get_42, len) == 42 @@ -1652,7 +1553,6 @@ def ok_or[E](self, err: E) -> Result[T, E]: True - .. code-block:: python >>> Null().ok_or(0) == Err(error=0) @@ -1674,7 +1574,6 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: True - .. code-block:: python >>> Null().ok_or_else(get_42) == Err(error=42) @@ -1696,21 +1595,18 @@ def or_(self, opt_b: Option[T]) -> Option[T]: True - .. code-block:: python >>> Null().or_(Some(inner=100)) == Some(inner=100) True - .. code-block:: python >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True - .. code-block:: python >>> Null().or_(Null()) == Null() @@ -1730,14 +1626,12 @@ def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: True - .. code-block:: python >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True - .. code-block:: python >>> Null().or_else(Null) == Null() @@ -1757,7 +1651,6 @@ def replace(self, value: T) -> Option[T]: # noqa: ARG002 True - .. code-block:: python >>> Null().replace(3) == Null() @@ -1777,14 +1670,12 @@ def transpose[E](self) -> Result[Option[T], E]: True - .. code-block:: python >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True - .. code-block:: python >>> Null().transpose() == Ok(inner=Null()) @@ -1819,7 +1710,6 @@ def unwrap_or(self, default: T) -> T: True - .. code-block:: python >>> Null().unwrap_or("bike") == "bike" @@ -1839,7 +1729,6 @@ def unwrap_or_else(self, fn: Callable[[], T]) -> T: True - .. code-block:: python >>> Null().unwrap_or_else(get_42) == 42 @@ -1859,14 +1748,12 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: # noqa: ARG002 True - .. code-block:: python >>> Some(inner=1).zip(Null()) == Null() True - .. code-block:: python >>> Null().zip(Some(inner=1)) == Null() @@ -1886,14 +1773,12 @@ def unzip[U](self) -> tuple[Option[T], Option[U]]: True - .. code-block:: python >>> Some(inner=4).unzip() == (Null(), Null()) True - .. code-block:: python >>> Null().unzip() == (Null(), Null()) diff --git a/src/danom/_monads/_result_v2.py b/src/danom/_monads/_result_v2.py index 3be799b..36273f7 100644 --- a/src/danom/_monads/_result_v2.py +++ b/src/danom/_monads/_result_v2.py @@ -71,21 +71,18 @@ def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. .. code-block:: python >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. .. code-block:: python >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True - Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned. .. code-block:: python @@ -108,14 +105,12 @@ def and_then[U, F, **P]( >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result. .. code-block:: python >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - An existing ``Err`` passes through ``and_then`` unchanged, without calling the function. .. code-block:: python @@ -135,7 +130,6 @@ def cloned(self) -> Result[T, E]: >>> Ok(inner=2).cloned() == Ok(inner=2) True - Cloning an ``Err`` creates a separate ``Err`` with the same value. .. code-block:: python @@ -156,7 +150,6 @@ def err(self) -> Option[E]: >>> Ok(inner=2).err() == Null() True - If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned. .. code-block:: python @@ -205,21 +198,18 @@ def flatten(self) -> Result[T, E]: >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True - This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad. .. code-block:: python >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True - Calling ``flatten`` if the inner is not a monad of the same type is a no-op. .. code-block:: python >>> Ok(inner=2).flatten() == Ok(inner=2) True - An ``Err`` passes through ``flatten`` unchanged. .. code-block:: python @@ -240,7 +230,6 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, E]: >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called. .. code-block:: python @@ -261,7 +250,6 @@ def inspect_err(self, fn: Callable[[E], None]) -> Result[T, E]: >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True - Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called. .. code-block:: python @@ -282,7 +270,6 @@ def is_err(self) -> bool: >>> Ok(inner=2).is_err() == False True - For an ``Err``, ``is_err`` reports ``True``. .. code-block:: python @@ -303,14 +290,12 @@ def is_err_and(self, fn: Callable[[E], bool]) -> bool: >>> Err(error=1).is_err_and(is_even) == False True - Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned. .. code-block:: python >>> Err(error=2).is_err_and(is_even) == True True - Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called. .. code-block:: python @@ -331,7 +316,6 @@ def is_ok(self) -> bool: >>> Ok(inner=2).is_ok() == True True - For an ``Err``, ``is_ok`` reports ``False``. .. code-block:: python @@ -352,14 +336,12 @@ def is_ok_and(self, fn: Callable[[T], bool]) -> bool: >>> Ok(inner=1).is_ok_and(is_even) == False True - When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``. .. code-block:: python >>> Ok(inner=2).is_ok_and(is_even) == True True - An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -382,7 +364,6 @@ def map[U, **P]( >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - Mapping an ``Err`` leaves it unchanged and skips the function. .. code-block:: python @@ -405,7 +386,6 @@ def map_err[F, **P]( >>> Err(error=1).map_err(add_one) == Err(error=2) True - Mapping an ``Ok`` leaves it unchanged and skips the function. .. code-block:: python @@ -428,7 +408,6 @@ def map_or[U, **P]( >>> Ok(inner="foo").map_or(42, len) == 3 True - For an ``Err``, ``map_or`` returns the supplied default. .. code-block:: python @@ -455,7 +434,6 @@ def map_or_else[U, **P]( >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True - An ``Err`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -476,7 +454,6 @@ def ok(self) -> Option[T]: >>> Ok(inner=2).ok() == Some(inner=2) True - An ``Err`` converts to ``Null`` through ``ok``. .. code-block:: python @@ -497,21 +474,18 @@ def or_[F](self, res: Result[T, F]) -> Result[T, F]: >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - An ``Err`` gives way to an ``Ok`` passed to ``or_``. .. code-block:: python >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True - When both values are ``Ok``, ``or_`` keeps the first one. .. code-block:: python >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - When both values are ``Err``, ``or_`` keeps the first error. .. code-block:: python @@ -534,14 +508,12 @@ def or_else[F, **P]( >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - If the fallback also produces an ``Err``, ``or_else`` keeps the original error. .. code-block:: python @@ -562,14 +534,12 @@ def transpose(self) -> Option[Result[T, E]]: >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True - Transposing ``Ok(Null())`` produces ``Null``. .. code-block:: python >>> Ok(inner=Null()).transpose() == Null() True - Transposing an ``Err`` wraps it in ``Some``. .. code-block:: python @@ -618,7 +588,6 @@ def unwrap_or(self, default: T) -> T: >>> Ok(inner="car").unwrap_or("bike") == "car" True - With an ``Err``, ``unwrap_or`` returns the default. .. code-block:: python @@ -639,7 +608,6 @@ def unwrap_or_else(self, fn: Callable[[E], T]) -> T: >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - An ``Err`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -664,21 +632,18 @@ def and_[U, E](self, res: Result[U, E]) -> Result[U, E]: >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. .. code-block:: python >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. .. code-block:: python >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True - Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned. .. code-block:: python @@ -700,14 +665,12 @@ def and_then[U, E, **P]( >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result. .. code-block:: python >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - An existing ``Err`` passes through ``and_then`` unchanged, without calling the function. .. code-block:: python @@ -727,7 +690,6 @@ def err[E](self) -> Option[E]: >>> Ok(inner=2).err() == Null() True - If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned. .. code-block:: python @@ -775,21 +737,18 @@ def flatten(self) -> Result[T, Never]: >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True - This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad. .. code-block:: python >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True - Calling ``flatten`` if the inner is not a monad of the same type is a no-op. .. code-block:: python >>> Ok(inner=2).flatten() == Ok(inner=2) True - An ``Err`` passes through ``flatten`` unchanged. .. code-block:: python @@ -811,7 +770,6 @@ def inspect(self, fn: Callable[[T], None]) -> Result[T, Never]: >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called. .. code-block:: python @@ -832,7 +790,6 @@ def inspect_err(self, fn: Callable[[Never], None]) -> Result[T, Never]: # noqa: >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True - Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called. .. code-block:: python @@ -852,7 +809,6 @@ def is_err(self) -> bool: >>> Ok(inner=2).is_err() == False True - For an ``Err``, ``is_err`` reports ``True``. .. code-block:: python @@ -872,14 +828,12 @@ def is_err_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 >>> Err(error=1).is_err_and(is_even) == False True - Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned. .. code-block:: python >>> Err(error=2).is_err_and(is_even) == True True - Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called. .. code-block:: python @@ -899,7 +853,6 @@ def is_ok(self) -> bool: >>> Ok(inner=2).is_ok() == True True - For an ``Err``, ``is_ok`` reports ``False``. .. code-block:: python @@ -919,14 +872,12 @@ def is_ok_and(self, fn: Callable[[T], bool]) -> bool: >>> Ok(inner=1).is_ok_and(is_even) == False True - When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``. .. code-block:: python >>> Ok(inner=2).is_ok_and(is_even) == True True - An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -948,7 +899,6 @@ def map[U, **P]( >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - Mapping an ``Err`` leaves it unchanged and skips the function. .. code-block:: python @@ -973,7 +923,6 @@ def map_err[F, **P]( >>> Err(error=1).map_err(add_one) == Err(error=2) True - Mapping an ``Ok`` leaves it unchanged and skips the function. .. code-block:: python @@ -999,7 +948,6 @@ def map_or[U, **P]( >>> Ok(inner="foo").map_or(42, len) == 3 True - For an ``Err``, ``map_or`` returns the supplied default. .. code-block:: python @@ -1025,7 +973,6 @@ def map_or_else[U, **P]( >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True - An ``Err`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -1045,7 +992,6 @@ def ok(self) -> Option[T]: >>> Ok(inner=2).ok() == Some(inner=2) True - An ``Err`` converts to ``Null`` through ``ok``. .. code-block:: python @@ -1067,21 +1013,18 @@ def or_[F](self, res: Result[T, F]) -> Result[T, F]: # noqa: ARG002 >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - An ``Err`` gives way to an ``Ok`` passed to ``or_``. .. code-block:: python >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True - When both values are ``Ok``, ``or_`` keeps the first one. .. code-block:: python >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - When both values are ``Err``, ``or_`` keeps the first error. .. code-block:: python @@ -1106,14 +1049,12 @@ def or_else[F, **P]( >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - If the fallback also produces an ``Err``, ``or_else`` keeps the original error. .. code-block:: python @@ -1133,14 +1074,12 @@ def transpose(self) -> Option[Result[T, Never]]: >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True - Transposing ``Ok(Null())`` produces ``Null``. .. code-block:: python >>> Ok(inner=Null()).transpose() == Null() True - Transposing an ``Err`` wraps it in ``Some``. .. code-block:: python @@ -1192,7 +1131,6 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 >>> Ok(inner="car").unwrap_or("bike") == "car" True - With an ``Err``, ``unwrap_or`` returns the default. .. code-block:: python @@ -1212,7 +1150,6 @@ def unwrap_or_else(self, fn: Callable[[Never], T]) -> T: # noqa: ARG002 >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - An ``Err`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -1245,21 +1182,18 @@ def and_[U, F](self, res: Result[U, F]) -> Result[U, F]: # noqa: ARG002 >>> Ok(inner=2).and_(Err(error="late error")) == Err(error="late error") True - If you call ``and_`` on an ``Err`` with an ``Ok`` as the arg then the ``Err`` still takes precedence over the ``Ok``. .. code-block:: python >>> Err(error="early error").and_(Ok(inner="foo")) == Err(error="early error") True - If you call ``and_`` on an ``Err`` with an ``Err`` as the arg then the first ``Err`` is returned and the second is discarded. .. code-block:: python >>> Err(error="not a 2").and_(Err(error="late error")) == Err(error="not a 2") True - Whereas, calling ``and_`` on an ``Ok`` with another ``Ok`` as the arg then the first ``Ok`` is discarded and the second is returned. .. code-block:: python @@ -1284,14 +1218,12 @@ def and_then[U, F, **P]( >>> Ok(inner=2).and_then(must_be_less_than_10) == Ok(inner=2) True - When the function passed to ``and_then`` produces an ``Err``, that ``Err`` becomes the result. .. code-block:: python >>> Ok(inner=20).and_then(must_be_less_than_10) == Err(error="too high") True - An existing ``Err`` passes through ``and_then`` unchanged, without calling the function. .. code-block:: python @@ -1311,7 +1243,6 @@ def err(self) -> Option[E]: >>> Ok(inner=2).err() == Null() True - If you call ``err`` on an ``Err`` then ``Some`` with the wrapped error is returned. .. code-block:: python @@ -1359,21 +1290,18 @@ def flatten(self) -> Result[Never, E]: >>> Ok(inner=Ok(inner=Ok(inner=2))).flatten() == Ok(inner=Ok(inner=2)) True - This is made obvious calling ``flatten`` on a doubly wrapped value returning just the inner monad. .. code-block:: python >>> Ok(inner=Ok(inner=2)).flatten() == Ok(inner=2) True - Calling ``flatten`` if the inner is not a monad of the same type is a no-op. .. code-block:: python >>> Ok(inner=2).flatten() == Ok(inner=2) True - An ``Err`` passes through ``flatten`` unchanged. .. code-block:: python @@ -1393,7 +1321,6 @@ def inspect(self, fn: Callable[[Never], None]) -> Result[Never, E]: # noqa: ARG >>> Ok(inner=[1]).inspect(append_to_list) == Ok(inner=[1]) True - If you call ``inspect`` on an ``Err`` then the ``Err`` is returned and the function is not called. .. code-block:: python @@ -1413,7 +1340,6 @@ def inspect_err(self, fn: Callable[[E], None]) -> Result[Never, E]: >>> Err(error=[1]).inspect_err(append_to_list) == Err(error=[1]) True - Similarly to calling ``inspect`` on an ``Err``, if you call ``inspect_err`` on an ``Ok`` then the ``Ok`` is returned and the function is not called. .. code-block:: python @@ -1434,7 +1360,6 @@ def is_err(self) -> bool: >>> Ok(inner=2).is_err() == False True - For an ``Err``, ``is_err`` reports ``True``. .. code-block:: python @@ -1454,14 +1379,12 @@ def is_err_and(self, fn: Callable[[E], bool]) -> bool: >>> Err(error=1).is_err_and(is_even) == False True - Therefore, if you call ``is_err_and`` on an ``Err`` with a function that returns ``True`` then ``True`` is returned. .. code-block:: python >>> Err(error=2).is_err_and(is_even) == True True - Whereas, if you call ``is_err_and`` on an ``Ok`` with a function then ``False`` is returned and the function is not called. .. code-block:: python @@ -1481,7 +1404,6 @@ def is_ok(self) -> bool: >>> Ok(inner=2).is_ok() == True True - For an ``Err``, ``is_ok`` reports ``False``. .. code-block:: python @@ -1501,14 +1423,12 @@ def is_ok_and(self, fn: Callable[[Never], bool]) -> bool: # noqa: ARG002 >>> Ok(inner=1).is_ok_and(is_even) == False True - When the predicate accepts an ``Ok``, ``is_ok_and`` reports ``True``. .. code-block:: python >>> Ok(inner=2).is_ok_and(is_even) == True True - An ``Err`` makes ``is_ok_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -1533,7 +1453,6 @@ def map[U, **P]( >>> Ok(inner=1).map(add_one) == Ok(inner=2) True - Mapping an ``Err`` leaves it unchanged and skips the function. .. code-block:: python @@ -1555,7 +1474,6 @@ def map_err[F, **P]( >>> Err(error=1).map_err(add_one) == Err(error=2) True - Mapping an ``Ok`` leaves it unchanged and skips the function. .. code-block:: python @@ -1583,7 +1501,6 @@ def map_or[U, **P]( >>> Ok(inner="foo").map_or(42, len) == 3 True - For an ``Err``, ``map_or`` returns the supplied default. .. code-block:: python @@ -1609,7 +1526,6 @@ def map_or_else[U, **P]( >>> Ok(inner="foo").map_or_else(get_42, len) == 3 True - An ``Err`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -1629,7 +1545,6 @@ def ok(self) -> Option[Never]: >>> Ok(inner=2).ok() == Some(inner=2) True - An ``Err`` converts to ``Null`` through ``ok``. .. code-block:: python @@ -1651,21 +1566,18 @@ def or_[F](self, res: Result[Never, F]) -> Result[Never, F]: >>> Ok(inner=2).or_(Err(error="foo")) == Ok(inner=2) True - An ``Err`` gives way to an ``Ok`` passed to ``or_``. .. code-block:: python >>> Err(error="foo").or_(Ok(inner=100)) == Ok(inner=100) True - When both values are ``Ok``, ``or_`` keeps the first one. .. code-block:: python >>> Ok(inner=2).or_(Ok(inner=100)) == Ok(inner=2) True - When both values are ``Err``, ``or_`` keeps the first error. .. code-block:: python @@ -1687,14 +1599,12 @@ def or_else[F, **P]( >>> Ok(inner="barbarians").or_else(get_ok_vikings) == Ok(inner="barbarians") True - For an ``Err``, ``or_else`` returns the ``Ok`` produced by the function. .. code-block:: python >>> Err(error="foo").or_else(get_ok_vikings) == Ok(inner="vikings") True - If the fallback also produces an ``Err``, ``or_else`` keeps the original error. .. code-block:: python @@ -1714,14 +1624,12 @@ def transpose(self) -> Option[Result[Never, E]]: >>> Ok(inner=Some(inner=5)).transpose() == Some(inner=Ok(inner=5)) True - Transposing ``Ok(Null())`` produces ``Null``. .. code-block:: python >>> Ok(inner=Null()).transpose() == Null() True - Transposing an ``Err`` wraps it in ``Some``. .. code-block:: python @@ -1771,7 +1679,6 @@ def unwrap_or[U](self, default: U) -> U: >>> Ok(inner="car").unwrap_or("bike") == "car" True - With an ``Err``, ``unwrap_or`` returns the default. .. code-block:: python @@ -1791,7 +1698,6 @@ def unwrap_or_else[U](self, fn: Callable[[E], U]) -> U: >>> Ok(inner=4).unwrap_or_else(get_42) == 4 True - An ``Err`` makes ``unwrap_or_else`` return the function result. .. code-block:: python From 1669e305769b68bfe158580f0b32efc3e0789d2d Mon Sep 17 00:00:00 2001 From: ed cuss Date: Sat, 26 Sep 2026 20:41:11 +0100 Subject: [PATCH 11/11] docs: id docs for option --- src/danom/_monads/_option.py | 388 +++++++++++++------------- tests/monads/test_option.py | 516 +++++++++++++++++++++++++++-------- 2 files changed, 601 insertions(+), 303 deletions(-) diff --git a/src/danom/_monads/_option.py b/src/danom/_monads/_option.py index 3bf4852..0c7c23e 100644 --- a/src/danom/_monads/_option.py +++ b/src/danom/_monads/_option.py @@ -17,25 +17,25 @@ class Option[T](ABC): def and_(self, opt_b: Option[T]) -> Option[T]: """Papertrail examples: - + A ``Some`` combined with ``Null`` through ``and_`` produces ``Null``. .. code-block:: python >>> Some(inner=2).and_(Null()) == Null() True - + A ``Null`` combined with an ``Some`` through ``and_`` remains ``Null``. .. code-block:: python >>> Null().and_(Some(inner="foo")) == Null() True - + When both options contain values, ``and_`` keeps the second ``Some``. .. code-block:: python >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True - + Combining two ``Null`` values with ``and_`` produces ``Null``. .. code-block:: python @@ -49,19 +49,19 @@ def and_(self, opt_b: Option[T]) -> Option[T]: def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: """Papertrail examples: - + A successful function passed to ``and_then`` returns its ``Some`` result. .. code-block:: python >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) True - + When the function returns ``Null``, ``and_then`` passes that ``Null`` through. .. code-block:: python >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True - + An existing ``Null`` skips the function passed to ``and_then``. .. code-block:: python @@ -75,13 +75,13 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[T]: def as_list(self) -> list[T]: """Papertrail examples: - + A ``Some`` becomes a one-item list through ``as_list``. .. code-block:: python >>> Some(inner=2).as_list() == [2] True - + ``as_list`` represents ``Null`` as an empty list. .. code-block:: python @@ -95,13 +95,13 @@ def as_list(self) -> list[T]: def as_tuple(self) -> tuple[T, ...]: """Papertrail examples: - + A ``Some`` becomes a one-item tuple through ``as_tuple``. .. code-block:: python >>> Some(inner=2).as_tuple() == (2,) True - + ``as_tuple`` represents ``Null`` as an empty tuple. .. code-block:: python @@ -114,13 +114,13 @@ def as_tuple(self) -> tuple[T, ...]: def cloned(self) -> Option[T]: """Papertrail examples: - + Cloning a ``Some`` creates a separate option with the same value. .. code-block:: python >>> Some(inner=2).cloned() == Some(inner=2) True - + Cloning ``Null`` creates a separate ``Null``. .. code-block:: python @@ -134,7 +134,7 @@ def cloned(self) -> Option[T]: def expect(self, msg: str) -> T: """Papertrail examples: - + ``expect`` extracts the value from a ``Some``. .. code-block:: python @@ -148,19 +148,19 @@ def expect(self, msg: str) -> T: def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: """Papertrail examples: - + A ``Some`` that fails the predicate becomes ``Null`` through ``filter_``. .. code-block:: python >>> Some(inner=3).filter_(is_even) == Null() True - + A ``Some`` that passes the predicate remains unchanged. .. code-block:: python >>> Some(inner=4).filter_(is_even) == Some(inner=4) True - + ``filter_`` leaves ``Null`` unchanged without calling the predicate. .. code-block:: python @@ -174,25 +174,25 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: def flatten(self) -> Option[T]: """Papertrail examples: - + Flattening three nested ``Some`` values removes only the outer option. .. code-block:: python >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) True - + Flattening a doubly wrapped value returns the inner ``Some``. .. code-block:: python >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True - + Flattening a ``Some`` with a non-option value does nothing. .. code-block:: python >>> Some(inner=2).flatten() == Some(inner=2) True - + A ``Null`` passes through ``flatten`` unchanged. .. code-block:: python @@ -206,13 +206,13 @@ def flatten(self) -> Option[T]: def inspect(self, fn: Callable[[T], None]) -> Option[T]: """Papertrail examples: - + Inspecting a ``Some`` calls the function and returns the original option. .. code-block:: python >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) True - + Inspecting ``Null`` returns ``Null`` without calling the function. .. code-block:: python @@ -226,13 +226,13 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: def is_none(self) -> bool: """Papertrail examples: - + ``is_none`` reports ``False`` for a ``Some``. .. code-block:: python >>> Some(inner=2).is_none() == False True - + For ``Null``, ``is_none`` reports ``True``. .. code-block:: python @@ -246,19 +246,19 @@ def is_none(self) -> bool: def is_none_or(self, fn: Callable[[T], bool]) -> bool: """Papertrail examples: - + When the predicate rejects a ``Some``, ``is_none_or`` reports ``False``. .. code-block:: python >>> Some(inner=1).is_none_or(is_even) == False True - + When the predicate accepts a ``Some``, ``is_none_or`` reports ``True``. .. code-block:: python >>> Some(inner=2).is_none_or(is_even) == True True - + ``Null`` makes ``is_none_or`` report ``True`` without calling the predicate. .. code-block:: python @@ -272,13 +272,13 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: def is_some(self) -> bool: """Papertrail examples: - + ``is_some`` reports ``True`` for a ``Some``. .. code-block:: python >>> Some(inner=2).is_some() == True True - + For ``Null``, ``is_some`` reports ``False``. .. code-block:: python @@ -292,19 +292,19 @@ def is_some(self) -> bool: def is_some_and(self, fn: Callable[[T], bool]) -> bool: """Papertrail examples: - + When the predicate rejects a ``Some``, ``is_some_and`` reports ``False``. .. code-block:: python >>> Some(inner=1).is_some_and(is_even) == False True - + When the predicate accepts a ``Some``, ``is_some_and`` reports ``True``. .. code-block:: python >>> Some(inner=2).is_some_and(is_even) == True True - + A ``Null`` makes ``is_some_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -318,13 +318,13 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: def map[U](self, fn: Callable[[T], U]) -> Option[U]: """Papertrail examples: - + Mapping a ``Some`` applies the function and wraps its result in a new ``Some``. .. code-block:: python >>> Some(inner=1).map(add_one) == Some(inner=2) True - + Mapping ``Null`` leaves it unchanged and skips the function. .. code-block:: python @@ -338,13 +338,13 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: """Papertrail examples: - + For a ``Some``, ``map_or`` uses the function result instead of the default. .. code-block:: python >>> Some(inner="foo").map_or(42, len) == 3 True - + For ``Null``, ``map_or`` returns the supplied default. .. code-block:: python @@ -358,13 +358,13 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: """Papertrail examples: - + A ``Some`` makes ``map_or_else`` use the mapping function. .. code-block:: python >>> Some(inner="foo").map_or_else(get_42, len) == 3 True - + A ``Null`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -378,13 +378,13 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: def ok_or[E](self, err: E) -> Result[T, E]: """Papertrail examples: - + A ``Some`` converts to ``Ok`` through ``ok_or``. .. code-block:: python >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True - + A ``Null`` converts to ``Err`` through ``ok_or``. .. code-block:: python @@ -398,13 +398,13 @@ def ok_or[E](self, err: E) -> Result[T, E]: def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: """Papertrail examples: - + A ``Some`` converts to ``Ok`` without calling the error function. .. code-block:: python >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True - + A ``Null`` converts to ``Err`` using the error function result. .. code-block:: python @@ -418,25 +418,25 @@ def ok_or_else[E](self, err: Callable[..., E]) -> Result[T, E]: def or_(self, opt_b: Option[T]) -> Option[T]: """Papertrail examples: - + A ``Some`` keeps its value when ``or_`` receives ``Null``. .. code-block:: python >>> Some(inner=2).or_(Null()) == Some(inner=2) True - + A ``Null`` gives way to a ``Some`` passed to ``or_``. .. code-block:: python >>> Null().or_(Some(inner=100)) == Some(inner=100) True - + When both options contain values, ``or_`` keeps the first ``Some``. .. code-block:: python >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True - + When both options are ``Null``, ``or_`` returns ``Null``. .. code-block:: python @@ -450,19 +450,19 @@ def or_(self, opt_b: Option[T]) -> Option[T]: def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: """Papertrail examples: - + A ``Some`` passes through ``or_else`` without calling the function. .. code-block:: python >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") True - + For ``Null``, ``or_else`` returns the ``Some`` produced by the function. .. code-block:: python >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True - + If the fallback also produces ``Null``, ``or_else`` returns ``Null``. .. code-block:: python @@ -476,13 +476,13 @@ def or_else(self, opt_b: Callable[..., Option[T]]) -> Option[T]: def replace(self, value: T) -> Option[T]: """Papertrail examples: - + Replacing a ``Some`` returns a new ``Some`` with the replacement value. .. code-block:: python >>> Some(inner=2).replace(5) == Some(inner=5) True - + Replacing ``Null`` leaves it as ``Null``. .. code-block:: python @@ -496,19 +496,19 @@ def replace(self, value: T) -> Option[T]: def transpose[E](self) -> Result[Option[T], E]: """Papertrail examples: - + Transposing ``Some(Ok(value))`` produces ``Ok(Some(value))``. .. code-block:: python >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True - + Transposing ``Some(Err(error))`` produces ``Err(Some(error))``. .. code-block:: python >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True - + Transposing ``Null`` produces ``Ok(Null())``. .. code-block:: python @@ -522,7 +522,7 @@ def transpose[E](self) -> Result[Option[T], E]: def unwrap(self) -> T: """Papertrail examples: - + ``unwrap`` extracts the value from a ``Some``. .. code-block:: python @@ -536,13 +536,13 @@ def unwrap(self) -> T: def unwrap_or(self, default: T) -> T: """Papertrail examples: - + With a ``Some``, ``unwrap_or`` returns the value and ignores the default. .. code-block:: python >>> Some(inner="car").unwrap_or("bike") == "car" True - + With ``Null``, ``unwrap_or`` returns the default. .. code-block:: python @@ -556,13 +556,13 @@ def unwrap_or(self, default: T) -> T: def unwrap_or_else(self, fn: Callable[..., T]) -> T: """Papertrail examples: - + A ``Some`` makes ``unwrap_or_else`` return its value without calling the function. .. code-block:: python >>> Some(inner=4).unwrap_or_else(get_42) == 4 True - + A ``Null`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -576,19 +576,19 @@ def unwrap_or_else(self, fn: Callable[..., T]) -> T: def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: """Papertrail examples: - + Zipping two ``Some`` values produces a ``Some`` containing both values. .. code-block:: python >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) True - + Zipping a ``Some`` with ``Null`` produces ``Null``. .. code-block:: python >>> Some(inner=1).zip(Null()) == Null() True - + Zipping ``Null`` with a ``Some`` produces ``Null``. .. code-block:: python @@ -602,19 +602,19 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: def unzip[U](self) -> tuple[Option[T], Option[U]]: """Papertrail examples: - + Unzipping a ``Some`` pair produces two ``Some`` values. .. code-block:: python >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) True - + Unzipping a ``Some`` with a non-pair value produces two ``Null`` values. .. code-block:: python >>> Some(inner=4).unzip() == (Null(), Null()) True - + Unzipping ``Null`` produces two ``Null`` values. .. code-block:: python @@ -632,25 +632,25 @@ class Some[T](Option): def and_(self, opt_b: Option[T]) -> Option[T]: """Papertrail examples: - + A ``Some`` combined with ``Null`` through ``and_`` produces ``Null``. .. code-block:: python >>> Some(inner=2).and_(Null()) == Null() True - + A ``Null`` combined with an ``Some`` through ``and_`` remains ``Null``. .. code-block:: python >>> Null().and_(Some(inner="foo")) == Null() True - + When both options contain values, ``and_`` keeps the second ``Some``. .. code-block:: python >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True - + Combining two ``Null`` values with ``and_`` produces ``Null``. .. code-block:: python @@ -663,19 +663,19 @@ def and_(self, opt_b: Option[T]) -> Option[T]: def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: """Papertrail examples: - + A successful function passed to ``and_then`` returns its ``Some`` result. .. code-block:: python >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) True - + When the function returns ``Null``, ``and_then`` passes that ``Null`` through. .. code-block:: python >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True - + An existing ``Null`` skips the function passed to ``and_then``. .. code-block:: python @@ -688,13 +688,13 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: def as_list(self) -> list[T]: """Papertrail examples: - + A ``Some`` becomes a one-item list through ``as_list``. .. code-block:: python >>> Some(inner=2).as_list() == [2] True - + ``as_list`` represents ``Null`` as an empty list. .. code-block:: python @@ -707,13 +707,13 @@ def as_list(self) -> list[T]: def as_tuple(self) -> tuple[T, ...]: """Papertrail examples: - + A ``Some`` becomes a one-item tuple through ``as_tuple``. .. code-block:: python >>> Some(inner=2).as_tuple() == (2,) True - + ``as_tuple`` represents ``Null`` as an empty tuple. .. code-block:: python @@ -726,7 +726,7 @@ def as_tuple(self) -> tuple[T, ...]: def expect(self, msg: str) -> T: # noqa: ARG002 """Papertrail examples: - + ``expect`` extracts the value from a ``Some``. .. code-block:: python @@ -739,19 +739,19 @@ def expect(self, msg: str) -> T: # noqa: ARG002 def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: """Papertrail examples: - + A ``Some`` that fails the predicate becomes ``Null`` through ``filter_``. .. code-block:: python >>> Some(inner=3).filter_(is_even) == Null() True - + A ``Some`` that passes the predicate remains unchanged. .. code-block:: python >>> Some(inner=4).filter_(is_even) == Some(inner=4) True - + ``filter_`` leaves ``Null`` unchanged without calling the predicate. .. code-block:: python @@ -764,25 +764,25 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: def flatten(self) -> Option[T]: """Papertrail examples: - + Flattening three nested ``Some`` values removes only the outer option. .. code-block:: python >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) True - + Flattening a doubly wrapped value returns the inner ``Some``. .. code-block:: python >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True - + Flattening a ``Some`` with a non-option value does nothing. .. code-block:: python >>> Some(inner=2).flatten() == Some(inner=2) True - + A ``Null`` passes through ``flatten`` unchanged. .. code-block:: python @@ -797,13 +797,13 @@ def flatten(self) -> Option[T]: def inspect(self, fn: Callable[[T], None]) -> Option[T]: """Papertrail examples: - + Inspecting a ``Some`` calls the function and returns the original option. .. code-block:: python >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) True - + Inspecting ``Null`` returns ``Null`` without calling the function. .. code-block:: python @@ -817,13 +817,13 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: def is_none(self) -> bool: """Papertrail examples: - + ``is_none`` reports ``False`` for a ``Some``. .. code-block:: python >>> Some(inner=2).is_none() == False True - + For ``Null``, ``is_none`` reports ``True``. .. code-block:: python @@ -836,19 +836,19 @@ def is_none(self) -> bool: def is_none_or(self, fn: Callable[[T], bool]) -> bool: """Papertrail examples: - + When the predicate rejects a ``Some``, ``is_none_or`` reports ``False``. .. code-block:: python >>> Some(inner=1).is_none_or(is_even) == False True - + When the predicate accepts a ``Some``, ``is_none_or`` reports ``True``. .. code-block:: python >>> Some(inner=2).is_none_or(is_even) == True True - + ``Null`` makes ``is_none_or`` report ``True`` without calling the predicate. .. code-block:: python @@ -861,13 +861,13 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: def is_some(self) -> bool: """Papertrail examples: - + ``is_some`` reports ``True`` for a ``Some``. .. code-block:: python >>> Some(inner=2).is_some() == True True - + For ``Null``, ``is_some`` reports ``False``. .. code-block:: python @@ -880,19 +880,19 @@ def is_some(self) -> bool: def is_some_and(self, fn: Callable[[T], bool]) -> bool: """Papertrail examples: - + When the predicate rejects a ``Some``, ``is_some_and`` reports ``False``. .. code-block:: python >>> Some(inner=1).is_some_and(is_even) == False True - + When the predicate accepts a ``Some``, ``is_some_and`` reports ``True``. .. code-block:: python >>> Some(inner=2).is_some_and(is_even) == True True - + A ``Null`` makes ``is_some_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -905,13 +905,13 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: def map[U](self, fn: Callable[[T], U]) -> Option[U]: """Papertrail examples: - + Mapping a ``Some`` applies the function and wraps its result in a new ``Some``. .. code-block:: python >>> Some(inner=1).map(add_one) == Some(inner=2) True - + Mapping ``Null`` leaves it unchanged and skips the function. .. code-block:: python @@ -924,13 +924,13 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 """Papertrail examples: - + For a ``Some``, ``map_or`` uses the function result instead of the default. .. code-block:: python >>> Some(inner="foo").map_or(42, len) == 3 True - + For ``Null``, ``map_or`` returns the supplied default. .. code-block:: python @@ -943,13 +943,13 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: # noqa: ARG002 """Papertrail examples: - + A ``Some`` makes ``map_or_else`` use the mapping function. .. code-block:: python >>> Some(inner="foo").map_or_else(get_42, len) == 3 True - + A ``Null`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -962,13 +962,13 @@ def map_or_else[U](self, default: Callable[[], U], fn: Callable[[T], U]) -> U: def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 """Papertrail examples: - + A ``Some`` converts to ``Ok`` through ``ok_or``. .. code-block:: python >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True - + A ``Null`` converts to ``Err`` through ``ok_or``. .. code-block:: python @@ -983,13 +983,13 @@ def ok_or[E](self, err: E) -> Result[T, E]: # noqa: ARG002 def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 """Papertrail examples: - + A ``Some`` converts to ``Ok`` without calling the error function. .. code-block:: python >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True - + A ``Null`` converts to ``Err`` using the error function result. .. code-block:: python @@ -1004,25 +1004,25 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: # noqa: ARG002 def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 """Papertrail examples: - + A ``Some`` keeps its value when ``or_`` receives ``Null``. .. code-block:: python >>> Some(inner=2).or_(Null()) == Some(inner=2) True - + A ``Null`` gives way to a ``Some`` passed to ``or_``. .. code-block:: python >>> Null().or_(Some(inner=100)) == Some(inner=100) True - + When both options contain values, ``or_`` keeps the first ``Some``. .. code-block:: python >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True - + When both options are ``Null``, ``or_`` returns ``Null``. .. code-block:: python @@ -1035,19 +1035,19 @@ def or_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: # noqa: ARG002 """Papertrail examples: - + A ``Some`` passes through ``or_else`` without calling the function. .. code-block:: python >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") True - + For ``Null``, ``or_else`` returns the ``Some`` produced by the function. .. code-block:: python >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True - + If the fallback also produces ``Null``, ``or_else`` returns ``Null``. .. code-block:: python @@ -1060,13 +1060,13 @@ def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: # noqa: ARG002 def replace(self, value: T) -> Option[T]: """Papertrail examples: - + Replacing a ``Some`` returns a new ``Some`` with the replacement value. .. code-block:: python >>> Some(inner=2).replace(5) == Some(inner=5) True - + Replacing ``Null`` leaves it as ``Null``. .. code-block:: python @@ -1079,19 +1079,19 @@ def replace(self, value: T) -> Option[T]: def transpose(self) -> Result[Option[T], Option[T]]: """Papertrail examples: - + Transposing ``Some(Ok(value))`` produces ``Ok(Some(value))``. .. code-block:: python >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True - + Transposing ``Some(Err(error))`` produces ``Err(Some(error))``. .. code-block:: python >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True - + Transposing ``Null`` produces ``Ok(Null())``. .. code-block:: python @@ -1110,7 +1110,7 @@ def transpose(self) -> Result[Option[T], Option[T]]: def unwrap(self) -> T: """Papertrail examples: - + ``unwrap`` extracts the value from a ``Some``. .. code-block:: python @@ -1123,13 +1123,13 @@ def unwrap(self) -> T: def unwrap_or(self, default: T) -> T: # noqa: ARG002 """Papertrail examples: - + With a ``Some``, ``unwrap_or`` returns the value and ignores the default. .. code-block:: python >>> Some(inner="car").unwrap_or("bike") == "car" True - + With ``Null``, ``unwrap_or`` returns the default. .. code-block:: python @@ -1142,13 +1142,13 @@ def unwrap_or(self, default: T) -> T: # noqa: ARG002 def unwrap_or_else(self, fn: Callable[[], T]) -> T: # noqa: ARG002 """Papertrail examples: - + A ``Some`` makes ``unwrap_or_else`` return its value without calling the function. .. code-block:: python >>> Some(inner=4).unwrap_or_else(get_42) == 4 True - + A ``Null`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -1161,19 +1161,19 @@ def unwrap_or_else(self, fn: Callable[[], T]) -> T: # noqa: ARG002 def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: """Papertrail examples: - + Zipping two ``Some`` values produces a ``Some`` containing both values. .. code-block:: python >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) True - + Zipping a ``Some`` with ``Null`` produces ``Null``. .. code-block:: python >>> Some(inner=1).zip(Null()) == Null() True - + Zipping ``Null`` with a ``Some`` produces ``Null``. .. code-block:: python @@ -1188,19 +1188,19 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: def unzip[U](self) -> tuple[Option[T], Option[U]]: """Papertrail examples: - + Unzipping a ``Some`` pair produces two ``Some`` values. .. code-block:: python >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) True - + Unzipping a ``Some`` with a non-pair value produces two ``Null`` values. .. code-block:: python >>> Some(inner=4).unzip() == (Null(), Null()) True - + Unzipping ``Null`` produces two ``Null`` values. .. code-block:: python @@ -1218,25 +1218,25 @@ class Null[T](Option): def and_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 """Papertrail examples: - + A ``Some`` combined with ``Null`` through ``and_`` produces ``Null``. .. code-block:: python >>> Some(inner=2).and_(Null()) == Null() True - + A ``Null`` combined with an ``Some`` through ``and_`` remains ``Null``. .. code-block:: python >>> Null().and_(Some(inner="foo")) == Null() True - + When both options contain values, ``and_`` keeps the second ``Some``. .. code-block:: python >>> Some(inner=2).and_(Some(inner="foo")) == Some(inner="foo") True - + Combining two ``Null`` values with ``and_`` produces ``Null``. .. code-block:: python @@ -1249,19 +1249,19 @@ def and_(self, opt_b: Option[T]) -> Option[T]: # noqa: ARG002 def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: # noqa: ARG002 """Papertrail examples: - + A successful function passed to ``and_then`` returns its ``Some`` result. .. code-block:: python >>> Some(inner=2).and_then(must_be_less_than_10) == Some(inner=2) True - + When the function returns ``Null``, ``and_then`` passes that ``Null`` through. .. code-block:: python >>> Some(inner=20).and_then(must_be_less_than_10) == Null() True - + An existing ``Null`` skips the function passed to ``and_then``. .. code-block:: python @@ -1274,13 +1274,13 @@ def and_then[U](self, fn: Callable[[T], Option[U]]) -> Option[U]: # noqa: ARG00 def as_list(self) -> list[T]: """Papertrail examples: - + A ``Some`` becomes a one-item list through ``as_list``. .. code-block:: python >>> Some(inner=2).as_list() == [2] True - + ``as_list`` represents ``Null`` as an empty list. .. code-block:: python @@ -1293,13 +1293,13 @@ def as_list(self) -> list[T]: def as_tuple(self) -> tuple[T, ...]: """Papertrail examples: - + A ``Some`` becomes a one-item tuple through ``as_tuple``. .. code-block:: python >>> Some(inner=2).as_tuple() == (2,) True - + ``as_tuple`` represents ``Null`` as an empty tuple. .. code-block:: python @@ -1312,7 +1312,7 @@ def as_tuple(self) -> tuple[T, ...]: def expect(self, msg: str) -> T: """Papertrail examples: - + ``expect`` extracts the value from a ``Some``. .. code-block:: python @@ -1325,19 +1325,19 @@ def expect(self, msg: str) -> T: def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: # noqa: ARG002 """Papertrail examples: - + A ``Some`` that fails the predicate becomes ``Null`` through ``filter_``. .. code-block:: python >>> Some(inner=3).filter_(is_even) == Null() True - + A ``Some`` that passes the predicate remains unchanged. .. code-block:: python >>> Some(inner=4).filter_(is_even) == Some(inner=4) True - + ``filter_`` leaves ``Null`` unchanged without calling the predicate. .. code-block:: python @@ -1350,25 +1350,25 @@ def filter_(self, predicate: Callable[[T], bool]) -> Option[T]: # noqa: ARG002 def flatten(self) -> Option[T]: """Papertrail examples: - + Flattening three nested ``Some`` values removes only the outer option. .. code-block:: python >>> Some(inner=Some(inner=Some(inner=2))).flatten() == Some(inner=Some(inner=2)) True - + Flattening a doubly wrapped value returns the inner ``Some``. .. code-block:: python >>> Some(inner=Some(inner=2)).flatten() == Some(inner=2) True - + Flattening a ``Some`` with a non-option value does nothing. .. code-block:: python >>> Some(inner=2).flatten() == Some(inner=2) True - + A ``Null`` passes through ``flatten`` unchanged. .. code-block:: python @@ -1381,13 +1381,13 @@ def flatten(self) -> Option[T]: def inspect(self, fn: Callable[[T], None]) -> Option[T]: # noqa: ARG002 """Papertrail examples: - + Inspecting a ``Some`` calls the function and returns the original option. .. code-block:: python >>> Some(inner=[1]).inspect(append_to_list) == Some(inner=[1]) True - + Inspecting ``Null`` returns ``Null`` without calling the function. .. code-block:: python @@ -1400,13 +1400,13 @@ def inspect(self, fn: Callable[[T], None]) -> Option[T]: # noqa: ARG002 def is_none(self) -> bool: """Papertrail examples: - + ``is_none`` reports ``False`` for a ``Some``. .. code-block:: python >>> Some(inner=2).is_none() == False True - + For ``Null``, ``is_none`` reports ``True``. .. code-block:: python @@ -1419,19 +1419,19 @@ def is_none(self) -> bool: def is_none_or(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 """Papertrail examples: - + When the predicate rejects a ``Some``, ``is_none_or`` reports ``False``. .. code-block:: python >>> Some(inner=1).is_none_or(is_even) == False True - + When the predicate accepts a ``Some``, ``is_none_or`` reports ``True``. .. code-block:: python >>> Some(inner=2).is_none_or(is_even) == True True - + ``Null`` makes ``is_none_or`` report ``True`` without calling the predicate. .. code-block:: python @@ -1444,13 +1444,13 @@ def is_none_or(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 def is_some(self) -> bool: """Papertrail examples: - + ``is_some`` reports ``True`` for a ``Some``. .. code-block:: python >>> Some(inner=2).is_some() == True True - + For ``Null``, ``is_some`` reports ``False``. .. code-block:: python @@ -1463,19 +1463,19 @@ def is_some(self) -> bool: def is_some_and(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 """Papertrail examples: - + When the predicate rejects a ``Some``, ``is_some_and`` reports ``False``. .. code-block:: python >>> Some(inner=1).is_some_and(is_even) == False True - + When the predicate accepts a ``Some``, ``is_some_and`` reports ``True``. .. code-block:: python >>> Some(inner=2).is_some_and(is_even) == True True - + A ``Null`` makes ``is_some_and`` report ``False`` without calling the predicate. .. code-block:: python @@ -1488,13 +1488,13 @@ def is_some_and(self, fn: Callable[[T], bool]) -> bool: # noqa: ARG002 def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 """Papertrail examples: - + Mapping a ``Some`` applies the function and wraps its result in a new ``Some``. .. code-block:: python >>> Some(inner=1).map(add_one) == Some(inner=2) True - + Mapping ``Null`` leaves it unchanged and skips the function. .. code-block:: python @@ -1507,13 +1507,13 @@ def map[U](self, fn: Callable[[T], U]) -> Option[U]: # noqa: ARG002 def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 """Papertrail examples: - + For a ``Some``, ``map_or`` uses the function result instead of the default. .. code-block:: python >>> Some(inner="foo").map_or(42, len) == 3 True - + For ``Null``, ``map_or`` returns the supplied default. .. code-block:: python @@ -1526,13 +1526,13 @@ def map_or[U](self, default: U, fn: Callable[[T], U]) -> U: # noqa: ARG002 def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: # noqa: ARG002 """Papertrail examples: - + A ``Some`` makes ``map_or_else`` use the mapping function. .. code-block:: python >>> Some(inner="foo").map_or_else(get_42, len) == 3 True - + A ``Null`` makes ``map_or_else`` use the default function. .. code-block:: python @@ -1545,13 +1545,13 @@ def map_or_else[U](self, default: Callable[..., U], fn: Callable[[T], U]) -> U: def ok_or[E](self, err: E) -> Result[T, E]: """Papertrail examples: - + A ``Some`` converts to ``Ok`` through ``ok_or``. .. code-block:: python >>> Some(inner="foo").ok_or(0) == Ok(inner="foo") True - + A ``Null`` converts to ``Err`` through ``ok_or``. .. code-block:: python @@ -1566,13 +1566,13 @@ def ok_or[E](self, err: E) -> Result[T, E]: def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: """Papertrail examples: - + A ``Some`` converts to ``Ok`` without calling the error function. .. code-block:: python >>> Some(inner="foo").ok_or_else(get_42) == Ok(inner="foo") True - + A ``Null`` converts to ``Err`` using the error function result. .. code-block:: python @@ -1587,25 +1587,25 @@ def ok_or_else[E](self, err: Callable[[], E]) -> Result[T, E]: def or_(self, opt_b: Option[T]) -> Option[T]: """Papertrail examples: - + A ``Some`` keeps its value when ``or_`` receives ``Null``. .. code-block:: python >>> Some(inner=2).or_(Null()) == Some(inner=2) True - + A ``Null`` gives way to a ``Some`` passed to ``or_``. .. code-block:: python >>> Null().or_(Some(inner=100)) == Some(inner=100) True - + When both options contain values, ``or_`` keeps the first ``Some``. .. code-block:: python >>> Some(inner=2).or_(Some(inner=100)) == Some(inner=2) True - + When both options are ``Null``, ``or_`` returns ``Null``. .. code-block:: python @@ -1618,19 +1618,19 @@ def or_(self, opt_b: Option[T]) -> Option[T]: def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: """Papertrail examples: - + A ``Some`` passes through ``or_else`` without calling the function. .. code-block:: python >>> Some(inner="barbarians").or_else(get_some_vikings) == Some(inner="barbarians") True - + For ``Null``, ``or_else`` returns the ``Some`` produced by the function. .. code-block:: python >>> Null().or_else(get_some_vikings) == Some(inner="vikings") True - + If the fallback also produces ``Null``, ``or_else`` returns ``Null``. .. code-block:: python @@ -1643,13 +1643,13 @@ def or_else(self, opt_b: Callable[[], Option[T]]) -> Option[T]: def replace(self, value: T) -> Option[T]: # noqa: ARG002 """Papertrail examples: - + Replacing a ``Some`` returns a new ``Some`` with the replacement value. .. code-block:: python >>> Some(inner=2).replace(5) == Some(inner=5) True - + Replacing ``Null`` leaves it as ``Null``. .. code-block:: python @@ -1662,19 +1662,19 @@ def replace(self, value: T) -> Option[T]: # noqa: ARG002 def transpose[E](self) -> Result[Option[T], E]: """Papertrail examples: - + Transposing ``Some(Ok(value))`` produces ``Ok(Some(value))``. .. code-block:: python >>> Some(inner=Ok(inner=2)).transpose() == Ok(inner=Some(inner=2)) True - + Transposing ``Some(Err(error))`` produces ``Err(Some(error))``. .. code-block:: python >>> Some(inner=Err(error=2)).transpose() == Err(error=Some(inner=2)) True - + Transposing ``Null`` produces ``Ok(Null())``. .. code-block:: python @@ -1689,7 +1689,7 @@ def transpose[E](self) -> Result[Option[T], E]: def unwrap(self) -> T: """Papertrail examples: - + ``unwrap`` extracts the value from a ``Some``. .. code-block:: python @@ -1702,13 +1702,13 @@ def unwrap(self) -> T: def unwrap_or(self, default: T) -> T: """Papertrail examples: - + With a ``Some``, ``unwrap_or`` returns the value and ignores the default. .. code-block:: python >>> Some(inner="car").unwrap_or("bike") == "car" True - + With ``Null``, ``unwrap_or`` returns the default. .. code-block:: python @@ -1721,13 +1721,13 @@ def unwrap_or(self, default: T) -> T: def unwrap_or_else(self, fn: Callable[[], T]) -> T: """Papertrail examples: - + A ``Some`` makes ``unwrap_or_else`` return its value without calling the function. .. code-block:: python >>> Some(inner=4).unwrap_or_else(get_42) == 4 True - + A ``Null`` makes ``unwrap_or_else`` return the function result. .. code-block:: python @@ -1740,19 +1740,19 @@ def unwrap_or_else(self, fn: Callable[[], T]) -> T: def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: # noqa: ARG002 """Papertrail examples: - + Zipping two ``Some`` values produces a ``Some`` containing both values. .. code-block:: python >>> Some(inner=1).zip(Some(inner="hi")) == Some(inner=(1, "hi")) True - + Zipping a ``Some`` with ``Null`` produces ``Null``. .. code-block:: python >>> Some(inner=1).zip(Null()) == Null() True - + Zipping ``Null`` with a ``Some`` produces ``Null``. .. code-block:: python @@ -1765,19 +1765,19 @@ def zip[U](self, other: Option[U]) -> Option[tuple[T, U]]: # noqa: ARG002 def unzip[U](self) -> tuple[Option[T], Option[U]]: """Papertrail examples: - + Unzipping a ``Some`` pair produces two ``Some`` values. .. code-block:: python >>> Some(inner=(2, 2)).unzip() == (Some(inner=2), Some(inner=2)) True - + Unzipping a ``Some`` with a non-pair value produces two ``Null`` values. .. code-block:: python >>> Some(inner=4).unzip() == (Null(), Null()) True - + Unzipping ``Null`` produces two ``Null`` values. .. code-block:: python diff --git a/tests/monads/test_option.py b/tests/monads/test_option.py index 828beb7..d737b70 100644 --- a/tests/monads/test_option.py +++ b/tests/monads/test_option.py @@ -10,14 +10,34 @@ @pytest.mark.parametrize( ("monad", "opt_b", "expected_result"), [ - pytest.param(Some(2), Null(), Null()), - pytest.param(Null(), Some("foo"), Null()), - pytest.param(Some(2), Some("foo"), Some("foo")), - pytest.param(Null(), Null(), Null()), + pytest.param( + Some(2), + Null(), + Null(), + id="A ``Some`` combined with ``Null`` through ``and_`` produces ``Null``.", + ), + pytest.param( + Null(), + Some("foo"), + Null(), + id="A ``Null`` combined with an ``Some`` through ``and_`` remains ``Null``.", + ), + pytest.param( + Some(2), + Some("foo"), + Some("foo"), + id="When both options contain values, ``and_`` keeps the second ``Some``.", + ), + pytest.param( + Null(), + Null(), + Null(), + id="Combining two ``Null`` values with ``and_`` produces ``Null``.", + ), ], ) -def test_and_(monad: Option, opt_b, expected_result) -> None: - assert example(monad.and_, opt_b) == expected_result +def test_and_(request, monad: Option, opt_b, expected_result) -> None: + assert example(monad.and_, opt_b, description=request.node.callspec.id) == expected_result def must_be_less_than_10(x: int) -> Option[int]: @@ -27,72 +47,137 @@ def must_be_less_than_10(x: int) -> Option[int]: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Some(2), must_be_less_than_10, Some(2)), - pytest.param(Some(20), must_be_less_than_10, Null()), - pytest.param(Null(), must_be_less_than_10, Null()), + pytest.param( + Some(2), + must_be_less_than_10, + Some(2), + id="A successful function passed to ``and_then`` returns its ``Some`` result.", + ), + pytest.param( + Some(20), + must_be_less_than_10, + Null(), + id="When the function returns ``Null``, ``and_then`` passes that ``Null`` through.", + ), + pytest.param( + Null(), + must_be_less_than_10, + Null(), + id="An existing ``Null`` skips the function passed to ``and_then``.", + ), ], ) -def test_and_then(monad: Option, fn, expected_result) -> None: - assert example(monad.and_then, fn) == expected_result +def test_and_then(request, monad: Option, fn, expected_result) -> None: + assert example(monad.and_then, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Some(2), [2]), pytest.param(Null(), [])] + ("monad", "expected_result"), + [ + pytest.param(Some(2), [2], id="A ``Some`` becomes a one-item list through ``as_list``."), + pytest.param(Null(), [], id="``as_list`` represents ``Null`` as an empty list."), + ], ) -def test_as_list(monad: Option, expected_result) -> None: - assert example(monad.as_list) == expected_result +def test_as_list(request, monad: Option, expected_result) -> None: + assert example(monad.as_list, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Some(2), (2,)), pytest.param(Null(), ())] + ("monad", "expected_result"), + [ + pytest.param(Some(2), (2,), id="A ``Some`` becomes a one-item tuple through ``as_tuple``."), + pytest.param(Null(), (), id="``as_tuple`` represents ``Null`` as an empty tuple."), + ], ) -def test_as_tuple(monad: Option, expected_result) -> None: - assert example(monad.as_tuple) == expected_result +def test_as_tuple(request, monad: Option, expected_result) -> None: + assert example(monad.as_tuple, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Some(2), Some(2)), pytest.param(Null(), Null())] + ("monad", "expected_result"), + [ + pytest.param( + Some(2), Some(2), id="Cloning a ``Some`` creates a separate option with the same value." + ), + pytest.param(Null(), Null(), id="Cloning ``Null`` creates a separate ``Null``."), + ], ) -def test_cloned(monad: Option, expected_result) -> None: - assert example(monad.cloned) == expected_result +def test_cloned(request, monad: Option, expected_result) -> None: + assert example(monad.cloned, description=request.node.callspec.id) == expected_result assert id(monad) != id(expected_result) @pytest.mark.parametrize( ("monad", "msg", "expected_result", "expected_context"), [ - pytest.param(Some(2), "must be positive", 2, nullcontext()), - pytest.param(Null(), "must be positive", None, pytest.raises(ValueError)), + pytest.param( + Some(2), + "must be positive", + 2, + nullcontext(), + id="``expect`` extracts the value from a ``Some``.", + ), + pytest.param( + Null(), + "must be positive", + None, + pytest.raises(ValueError), + id="``expect`` raises ``ValueError`` for ``Null`` and uses the supplied message.", + ), ], ) -def test_expect(monad: Option, msg, expected_result, expected_context) -> None: +def test_expect(request, monad: Option, msg, expected_result, expected_context) -> None: with expected_context: - assert example(monad.expect, msg) == expected_result + assert example(monad.expect, msg, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "predicate", "expected_result"), [ - pytest.param(Some(3), is_even, Null()), - pytest.param(Some(4), is_even, Some(4)), - pytest.param(Null(), is_even, Null()), + pytest.param( + Some(3), + is_even, + Null(), + id="A ``Some`` that fails the predicate becomes ``Null`` through ``filter_``.", + ), + pytest.param( + Some(4), is_even, Some(4), id="A ``Some`` that passes the predicate remains unchanged." + ), + pytest.param( + Null(), + is_even, + Null(), + id="``filter_`` leaves ``Null`` unchanged without calling the predicate.", + ), ], ) -def test_filter_(monad: Option, predicate, expected_result) -> None: - assert example(monad.filter_, predicate) == expected_result +def test_filter_(request, monad: Option, predicate, expected_result) -> None: + assert ( + example(monad.filter_, predicate, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( ("monad", "expected_result"), [ - pytest.param(Some(Some(Some(2))), Some(Some(2))), - pytest.param(Some(Some(2)), Some(2)), - pytest.param(Some(2), Some(2)), - pytest.param(Null(), Null()), + pytest.param( + Some(Some(Some(2))), + Some(Some(2)), + id="Flattening three nested ``Some`` values removes only the outer option.", + ), + pytest.param( + Some(Some(2)), + Some(2), + id="Flattening a doubly wrapped value returns the inner ``Some``.", + ), + pytest.param( + Some(2), Some(2), id="Flattening a ``Some`` with a non-option value does nothing." + ), + pytest.param(Null(), Null(), id="A ``Null`` passes through ``flatten`` unchanged."), ], ) -def test_flatten(monad: Option, expected_result) -> None: - assert example(monad.flatten) == expected_result +def test_flatten(request, monad: Option, expected_result) -> None: + assert example(monad.flatten, description=request.node.callspec.id) == expected_result def append_to_list(x) -> None: @@ -102,183 +187,396 @@ def append_to_list(x) -> None: @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Some([1]), append_to_list, Some([1])), - pytest.param(Null(), append_to_list, Null()), + pytest.param( + Some([1]), + append_to_list, + Some([1]), + id="Inspecting a ``Some`` calls the function and returns the original option.", + ), + pytest.param( + Null(), + append_to_list, + Null(), + id="Inspecting ``Null`` returns ``Null`` without calling the function.", + ), ], ) -def test_inspect(monad: Option, fn, expected_result) -> None: - assert example(monad.inspect, fn) == expected_result +def test_inspect(request, monad: Option, fn, expected_result) -> None: + assert example(monad.inspect, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Some(2), False), pytest.param(Null(), True)] + ("monad", "expected_result"), + [ + pytest.param(Some(2), False, id="``is_none`` reports ``False`` for a ``Some``."), + pytest.param(Null(), True, id="For ``Null``, ``is_none`` reports ``True``."), + ], ) -def test_is_none(monad: Option, expected_result) -> None: - assert example(monad.is_none) == expected_result +def test_is_none(request, monad: Option, expected_result) -> None: + assert example(monad.is_none, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Some(1), is_even, False), - pytest.param(Some(2), is_even, True), - pytest.param(Null(), is_even, True), + pytest.param( + Some(1), + is_even, + False, + id="When the predicate rejects a ``Some``, ``is_none_or`` reports ``False``.", + ), + pytest.param( + Some(2), + is_even, + True, + id="When the predicate accepts a ``Some``, ``is_none_or`` reports ``True``.", + ), + pytest.param( + Null(), + is_even, + True, + id="``Null`` makes ``is_none_or`` report ``True`` without calling the predicate.", + ), ], ) -def test_is_none_or(monad: Option, fn, expected_result) -> None: - assert example(monad.is_none_or, fn) == expected_result +def test_is_none_or(request, monad: Option, fn, expected_result) -> None: + assert example(monad.is_none_or, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( - ("monad", "expected_result"), [pytest.param(Some(2), True), pytest.param(Null(), False)] + ("monad", "expected_result"), + [ + pytest.param(Some(2), True, id="``is_some`` reports ``True`` for a ``Some``."), + pytest.param(Null(), False, id="For ``Null``, ``is_some`` reports ``False``."), + ], ) -def test_is_some(monad: Option, expected_result) -> None: - assert example(monad.is_some) == expected_result +def test_is_some(request, monad: Option, expected_result) -> None: + assert example(monad.is_some, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "fn", "expected_result"), [ - pytest.param(Some(1), is_even, False), - pytest.param(Some(2), is_even, True), - pytest.param(Null(), is_even, False), + pytest.param( + Some(1), + is_even, + False, + id="When the predicate rejects a ``Some``, ``is_some_and`` reports ``False``.", + ), + pytest.param( + Some(2), + is_even, + True, + id="When the predicate accepts a ``Some``, ``is_some_and`` reports ``True``.", + ), + pytest.param( + Null(), + is_even, + False, + id="A ``Null`` makes ``is_some_and`` report ``False`` without calling the predicate.", + ), ], ) -def test_is_some_and(monad: Option, fn, expected_result) -> None: - assert example(monad.is_some_and, fn) == expected_result +def test_is_some_and(request, monad: Option, fn, expected_result) -> None: + assert example(monad.is_some_and, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Some(1), add_one, Some(2)), pytest.param(Null(), add_one, Null())], + [ + pytest.param( + Some(1), + add_one, + Some(2), + id="Mapping a ``Some`` applies the function and wraps its result in a new ``Some``.", + ), + pytest.param( + Null(), + add_one, + Null(), + id="Mapping ``Null`` leaves it unchanged and skips the function.", + ), + ], ) -def test_map(monad: Option, fn, expected_result) -> None: - assert example(monad.map, fn) == expected_result +def test_map(request, monad: Option, fn, expected_result) -> None: + assert example(monad.map, fn, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), - [pytest.param(Some("foo"), 42, len, 3), pytest.param(Null(), 42, len, 42)], + [ + pytest.param( + Some("foo"), + 42, + len, + 3, + id="For a ``Some``, ``map_or`` uses the function result instead of the default.", + ), + pytest.param( + Null(), 42, len, 42, id="For ``Null``, ``map_or`` returns the supplied default." + ), + ], ) -def test_map_or(monad: Option, default, fn, expected_result) -> None: - assert example(monad.map_or, default, fn) == expected_result +def test_map_or(request, monad: Option, default, fn, expected_result) -> None: + assert ( + example(monad.map_or, default, fn, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( ("monad", "default", "fn", "expected_result"), - [pytest.param(Some("foo"), get_42, len, 3), pytest.param(Null(), get_42, len, 42)], + [ + pytest.param( + Some("foo"), + get_42, + len, + 3, + id="A ``Some`` makes ``map_or_else`` use the mapping function.", + ), + pytest.param( + Null(), get_42, len, 42, id="A ``Null`` makes ``map_or_else`` use the default function." + ), + ], ) -def test_map_or_else(monad: Option, default, fn, expected_result) -> None: - assert example(monad.map_or_else, default, fn) == expected_result +def test_map_or_else(request, monad: Option, default, fn, expected_result) -> None: + assert ( + example(monad.map_or_else, default, fn, description=request.node.callspec.id) + == expected_result + ) @pytest.mark.parametrize( ("monad", "err", "expected_result"), - [pytest.param(Some("foo"), 0, Ok("foo")), pytest.param(Null(), 0, Err(0))], + [ + pytest.param( + Some("foo"), 0, Ok("foo"), id="A ``Some`` converts to ``Ok`` through ``ok_or``." + ), + pytest.param(Null(), 0, Err(0), id="A ``Null`` converts to ``Err`` through ``ok_or``."), + ], ) -def test_ok_or(monad: Option, err, expected_result) -> None: - assert example(monad.ok_or, err) == expected_result +def test_ok_or(request, monad: Option, err, expected_result) -> None: + assert example(monad.ok_or, err, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "err", "expected_result"), - [pytest.param(Some("foo"), get_42, Ok("foo")), pytest.param(Null(), get_42, Err(42))], + [ + pytest.param( + Some("foo"), + get_42, + Ok("foo"), + id="A ``Some`` converts to ``Ok`` without calling the error function.", + ), + pytest.param( + Null(), + get_42, + Err(42), + id="A ``Null`` converts to ``Err`` using the error function result.", + ), + ], ) -def test_ok_or_else(monad: Option, err, expected_result) -> None: - assert example(monad.ok_or_else, err) == expected_result +def test_ok_or_else(request, monad: Option, err, expected_result) -> None: + assert example(monad.ok_or_else, err, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "opt_b", "expected_result"), [ - pytest.param(Some(2), Null(), Some(2)), - pytest.param(Null(), Some(100), Some(100)), - pytest.param(Some(2), Some(100), Some(2)), - pytest.param(Null(), Null(), Null()), + pytest.param( + Some(2), + Null(), + Some(2), + id="A ``Some`` keeps its value when ``or_`` receives ``Null``.", + ), + pytest.param( + Null(), Some(100), Some(100), id="A ``Null`` gives way to a ``Some`` passed to ``or_``." + ), + pytest.param( + Some(2), + Some(100), + Some(2), + id="When both options contain values, ``or_`` keeps the first ``Some``.", + ), + pytest.param( + Null(), Null(), Null(), id="When both options are ``Null``, ``or_`` returns ``Null``." + ), ], ) -def test_or_(monad: Option, opt_b, expected_result) -> None: - assert example(monad.or_, opt_b) == expected_result +def test_or_(request, monad: Option, opt_b, expected_result) -> None: + assert example(monad.or_, opt_b, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "opt_b", "expected_result"), [ - pytest.param(Some("barbarians"), get_some_vikings, Some("barbarians")), - pytest.param(Null(), get_some_vikings, Some("vikings")), - pytest.param(Null(), Null, Null()), + pytest.param( + Some("barbarians"), + get_some_vikings, + Some("barbarians"), + id="A ``Some`` passes through ``or_else`` without calling the function.", + ), + pytest.param( + Null(), + get_some_vikings, + Some("vikings"), + id="For ``Null``, ``or_else`` returns the ``Some`` produced by the function.", + ), + pytest.param( + Null(), + Null, + Null(), + id="If the fallback also produces ``Null``, ``or_else`` returns ``Null``.", + ), ], ) -def test_or_else(monad: Option, opt_b, expected_result) -> None: - assert example(monad.or_else, opt_b) == expected_result +def test_or_else(request, monad: Option, opt_b, expected_result) -> None: + assert example(monad.or_else, opt_b, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "value", "expected_result"), - [pytest.param(Some(2), 5, Some(5)), pytest.param(Null(), 3, Null())], + [ + pytest.param( + Some(2), + 5, + Some(5), + id="Replacing a ``Some`` returns a new ``Some`` with the replacement value.", + ), + pytest.param(Null(), 3, Null(), id="Replacing ``Null`` leaves it as ``Null``."), + ], ) -def test_replace(monad: Option, value, expected_result) -> None: - assert example(monad.replace, value) == expected_result +def test_replace(request, monad: Option, value, expected_result) -> None: + assert example(monad.replace, value, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result", "expected_context"), [ - pytest.param(Some(Ok(2)), Ok(Some(2)), nullcontext()), - pytest.param(Some(Err(2)), Err(Some(2)), nullcontext()), - pytest.param(Null(), Ok(Null()), nullcontext()), - pytest.param(Some(2), None, pytest.raises(TypeError)), + pytest.param( + Some(Ok(2)), + Ok(Some(2)), + nullcontext(), + id="Transposing ``Some(Ok(value))`` produces ``Ok(Some(value))``.", + ), + pytest.param( + Some(Err(2)), + Err(Some(2)), + nullcontext(), + id="Transposing ``Some(Err(error))`` produces ``Err(Some(error))``.", + ), + pytest.param( + Null(), Ok(Null()), nullcontext(), id="Transposing ``Null`` produces ``Ok(Null())``." + ), + pytest.param( + Some(2), + None, + pytest.raises(TypeError), + id="Transposing a ``Some`` with an unsupported value raises ``TypeError``.", + ), ], ) -def test_transpose(monad: Option, expected_result, expected_context) -> None: +def test_transpose(request, monad: Option, expected_result, expected_context) -> None: with expected_context: - assert example(monad.transpose) == expected_result + assert example(monad.transpose, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result", "expected_context"), - [pytest.param(Some(2), 2, nullcontext()), pytest.param(Null(), None, pytest.raises(TypeError))], + [ + pytest.param( + Some(2), 2, nullcontext(), id="``unwrap`` extracts the value from a ``Some``." + ), + pytest.param( + Null(), + None, + pytest.raises(TypeError), + id="``unwrap`` raises ``TypeError`` when the option is ``Null``.", + ), + ], ) -def test_unwrap(monad: Option, expected_result, expected_context) -> None: +def test_unwrap(request, monad: Option, expected_result, expected_context) -> None: with expected_context: - assert example(monad.unwrap) == expected_result + assert example(monad.unwrap, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "default", "expected_result"), - [pytest.param(Some("car"), "bike", "car"), pytest.param(Null(), "bike", "bike")], + [ + pytest.param( + Some("car"), + "bike", + "car", + id="With a ``Some``, ``unwrap_or`` returns the value and ignores the default.", + ), + pytest.param( + Null(), "bike", "bike", id="With ``Null``, ``unwrap_or`` returns the default." + ), + ], ) -def test_unwrap_or(monad: Option, default, expected_result) -> None: - assert example(monad.unwrap_or, default) == expected_result +def test_unwrap_or(request, monad: Option, default, expected_result) -> None: + assert ( + example(monad.unwrap_or, default, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( ("monad", "fn", "expected_result"), - [pytest.param(Some(4), get_42, 4), pytest.param(Null(), get_42, 42)], + [ + pytest.param( + Some(4), + get_42, + 4, + id="A ``Some`` makes ``unwrap_or_else`` return its value without calling the function.", + ), + pytest.param( + Null(), get_42, 42, id="A ``Null`` makes ``unwrap_or_else`` return the function result." + ), + ], ) -def test_unwrap_or_else(monad: Option, fn, expected_result) -> None: - assert example(monad.unwrap_or_else, fn) == expected_result +def test_unwrap_or_else(request, monad: Option, fn, expected_result) -> None: + assert ( + example(monad.unwrap_or_else, fn, description=request.node.callspec.id) == expected_result + ) @pytest.mark.parametrize( ("monad", "other", "expected_result"), [ - pytest.param(Some(1), Some("hi"), Some((1, "hi"))), - pytest.param(Some(1), Null(), Null()), - pytest.param(Null(), Some(1), Null()), + pytest.param( + Some(1), + Some("hi"), + Some((1, "hi")), + id="Zipping two ``Some`` values produces a ``Some`` containing both values.", + ), + pytest.param( + Some(1), Null(), Null(), id="Zipping a ``Some`` with ``Null`` produces ``Null``." + ), + pytest.param( + Null(), Some(1), Null(), id="Zipping ``Null`` with a ``Some`` produces ``Null``." + ), ], ) -def test_zip(monad: Option, other, expected_result) -> None: - assert example(monad.zip, other) == expected_result +def test_zip(request, monad: Option, other, expected_result) -> None: + assert example(monad.zip, other, description=request.node.callspec.id) == expected_result @pytest.mark.parametrize( ("monad", "expected_result"), [ - pytest.param(Some((2, 2)), (Some(2), Some(2))), - pytest.param(Some(4), (Null(), Null())), - pytest.param(Null(), (Null(), Null())), + pytest.param( + Some((2, 2)), + (Some(2), Some(2)), + id="Unzipping a ``Some`` pair produces two ``Some`` values.", + ), + pytest.param( + Some(4), + (Null(), Null()), + id="Unzipping a ``Some`` with a non-pair value produces two ``Null`` values.", + ), + pytest.param( + Null(), (Null(), Null()), id="Unzipping ``Null`` produces two ``Null`` values." + ), ], ) -def test_unzip(monad: Option, expected_result) -> None: - assert example(monad.unzip) == expected_result +def test_unzip(request, monad: Option, expected_result) -> None: + assert example(monad.unzip, description=request.node.callspec.id) == expected_result