Skip to content

feat request/suggestion: fuse results from multiple results into the args of one target function #2045

@second-ed

Description

@second-ed

A way to join the results from multiple stages to a single function fusing the results into the positional args of the target function.

Thinking something like this could work, let me know if you think it's worth cleaning up/documenting and writing all the tests for:

from typing import Iterable, Callable
import inspect
from returns.result import Success, Failure, Result


def fuse(results: Iterable[Result], target: Callable) -> Result:
    """Fuse the results from multiple containers into the args for a target function.

    Args:
        results (Iterable[Result]): The results from multiple container functions.
        target (Callable): The target function that receives the arguments.
    """
    successes, failures, invalid = [], [], []

    for res in results:
        match res:
            case Success(value):
                successes.append(value)
            case Failure(error):
                failures.append(error)
            case _:
                invalid.append(res)

    if invalid:
        return Failure(ValueError(f"Input args are not all Result types: {invalid}"))

    if failures:
        return Failure(ValueError(f"Not all results are Success: {failures}"))

    expected_args = len(inspect.signature(target).parameters)

    if len(successes) != expected_args:
        return Failure(
            ValueError(
                f"Expected {expected_args} args for `{target.__name__}`, but got {len(successes)}"
            )
        )
    try:
        return target(*successes)
    except Exception as e:
        return Failure(e)

Using this basic case function here:

def add(a, b) -> Result[int, str]:
    if a > 0:
        return Success(a + b)
    return Failure(f"Err: expected `a` > 0. Got `a`= {a}.")

Usage:

Happy path all args are expected

fuse([Success(1), Success(3)], add)

output:

<Success: 4>

Returns failure if some exist

fuse([Success(3), Failure("Err: expected `a` > 0. Got `a`= -2.")], add)

output:

<Failure: Not all results are Success: ['Err: expected `a` > 0. Got `a`= -2.']>

Catch all the failures and report them all (avoiding solving one issue then finding another)

fuse([Failure("Err: expected `a` > 0. Got `a`= -2."), Failure("Err: expected `a` > 0. Got `a`= -2.")], add)

output:

<Failure: Not all results are Success: ['Err: expected `a` > 0. Got `a`= -2.', 'Err: expected `a` > 0. Got `a`= -2.']>

Catch when someone is passing in a non Result type:

fuse([1, Failure("Err: expected `a` > 0. Got `a`= -2.")], add)

output:

<Failure: Input args are not all Result types: [1]>

catch if not given the right number of args:

fuse([Success(1)], add)

output:

<Failure: Expected 2 args for `add`, but got 1>

catch overall exceptions just in case:

fuse([Success(1), Success("1")], add)

output

<Failure: unsupported operand type(s) for +: 'int' and 'str'>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions