diff --git a/CHANGELOG.md b/CHANGELOG.md index b736bcd70..4e390a0af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,11 @@ See [0Ver](https://0ver.org/). - Improve inference of `ResultLike` objects when exception catching decorator is applied with explicit exception types - Add picky exceptions to `impure_safe` decorator like `safe` has. Issue #1543 +- Add partition function to result module. Issue #1905 - Adds `default_error` parameter to `returns.converters.maybe_to_result`, which provides a default error value for `Failure` + ### Misc - Now requires `mypy>=1.11` diff --git a/docs/pages/methods.rst b/docs/pages/methods.rst index 421ae2241..ce7cfa64d 100644 --- a/docs/pages/methods.rst +++ b/docs/pages/methods.rst @@ -76,6 +76,22 @@ Here's a full example: >>> error_handled = pointfree.bimap(lambda inr: inr + 1, lambda _: 0)(instance) >>> assert isinstance(methods.unwrap_or_failure(error_handled), int) +partition +~~~~~~~~~ + +:func:`partition ` is used to convert +list of :class:`~returns.interfaces.Unwrappable` +instances like :class:`~returns.result.Result`, +:class:`~returns.io.IOResult`, and :class:`~returns.maybe.Maybe` +to a tuple of two lists: successes and failures. + +.. code:: python + + >>> from returns.result import Failure, Success + >>> from returns.methods import partition + >>> results = [Success(1), Failure(2), Success(3), Failure(4)] + >>> partition(results) + ([1, 3], [2, 4]) API Reference ------------- diff --git a/returns/methods/__init__.py b/returns/methods/__init__.py index 5e9a0a923..0613f1f5c 100644 --- a/returns/methods/__init__.py +++ b/returns/methods/__init__.py @@ -1,4 +1,5 @@ from returns.methods.cond import cond as cond +from returns.methods.partition import partition as partition from returns.methods.unwrap_or_failure import ( unwrap_or_failure as unwrap_or_failure, ) diff --git a/returns/methods/partition.py b/returns/methods/partition.py new file mode 100644 index 000000000..f844fda7c --- /dev/null +++ b/returns/methods/partition.py @@ -0,0 +1,38 @@ + +from typing import Iterable, List, TypeVar + +from returns.interfaces.unwrappable import Unwrappable +from returns.primitives.exceptions import UnwrapFailedError + +_ValueType = TypeVar('_ValueType', covariant=True) +_ErrorType = TypeVar('_ErrorType', covariant=True) + + +def partition( + containers: Iterable[ + Unwrappable[_ValueType, _ErrorType], + ], +) -> tuple[List[_ValueType], List[_ErrorType]]: + """ + Partition a list of unwrappables into successful and failed values. + + Preserves order. + + .. code:: python + + >>> from returns.result import Failure, Success + >>> from returns.methods import partition + + >>> results = [Success(1), Failure(2), Success(3), Failure(4)] + >>> partition(results) + ([1, 3], [2, 4]) + + """ + successes: list[_ValueType] = [] + failures: list[_ErrorType] = [] + for container in containers: + try: + successes.append(container.unwrap()) + except UnwrapFailedError: + failures.append(container.failure()) + return successes, failures diff --git a/tests/test_methods/test_partition.py b/tests/test_methods/test_partition.py new file mode 100644 index 000000000..d68faefb8 --- /dev/null +++ b/tests/test_methods/test_partition.py @@ -0,0 +1,32 @@ + +import pytest + +from returns.io import IO, IOResult +from returns.maybe import Nothing, Some +from returns.methods import partition +from returns.result import Failure, Success + + +@pytest.mark.parametrize(('containers', 'expected'), [ + ( + (Success(1), Success(2), Failure(None), Success(3)), + ([1, 2, 3], [None]), + ), + ( + ( + IOResult.from_value(1), + IOResult.from_failure(2), + IOResult.from_value(3), + IOResult.from_failure(4), + ), + ([IO(1), IO(3)], [IO(2), IO(4)]), + ), + ( + (Some(1), Some(2), Nothing), + ([1, 2], [None]), + ), + ((), ([], [])), +]) +def test_partition(containers, expected): + """Test partition function.""" + assert partition(containers) == expected diff --git a/typesafety/test_methods/test_partition.yml b/typesafety/test_methods/test_partition.yml new file mode 100644 index 000000000..3f4eeba69 --- /dev/null +++ b/typesafety/test_methods/test_partition.yml @@ -0,0 +1,32 @@ +- case: partition_result + disable_cache: false + main: | + from typing import List + from returns.result import Success, Failure, Result + from returns.methods import partition + + x: List[Result[int, str]] + reveal_type(partition(x)) # N: Revealed type is "Tuple[builtins.list[builtins.int], builtins.list[builtins.str]]" + +- case: partition_io_results + disable_cache: false + main: | + from typing import Tuple + from returns.result import Success, Failure + from returns.methods import partition + from returns.io import IO, IOResult, IOSuccess + + x: Tuple[IOResult[int, str], IOResult[int, str]] + reveal_type(partition(x)) # N: Revealed type is "Tuple[builtins.list[returns.io.IO[builtins.int]], builtins.list[returns.io.IO[builtins.str]]]" + +- case: partition_maybe + disable_cache: false + main: | + from typing import List, Tuple + from returns.maybe import Maybe + from returns.methods import partition + + x: List[Maybe[int]] + + reveal_type(partition(x)) # N: Revealed type is "Tuple[builtins.list[builtins.int], builtins.list[None]]" +