Skip to content

Commit

Permalink
Merge pull request #12 from danielSanchezQ/transpose
Browse files Browse the repository at this point in the history
Result flatten and transpose implementations
  • Loading branch information
check69 committed May 11, 2022
2 parents ce5f48f + 95354a1 commit cc92f86
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 5 deletions.
77 changes: 74 additions & 3 deletions rusty_results/prelude.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abc import abstractmethod
from dataclasses import dataclass
from typing import TypeVar, Union, Callable, Generic, Iterator, Tuple, Dict, Any
from typing import cast, TypeVar, Union, Callable, Generic, Iterator, Tuple, Dict, Any
from rusty_results.exceptions import UnwrapException
try:
from pydantic.fields import ModelField
Expand Down Expand Up @@ -240,6 +240,16 @@ def flatten(self) -> "Option[T]":
"""
... # pragma: no cover

@abstractmethod
def transpose(self) -> "Result[Option[T], E]":
"""
Transposes an Option of a Result into a Result of an Option.
Empty will be mapped to Ok(Empty). Some(Ok(_)) and Some(Err(_)) will be mapped to Ok(Some(_)) and Err(_).
:return: `Result[Option[T], E]`
:raises TypeError if inner value is not a `Result`
"""
... # pragma: no cover

@abstractmethod
def __bool__(self) -> bool:
... # pragma: no cover
Expand Down Expand Up @@ -378,7 +388,7 @@ def zip(self, other: "Option[U]") -> "Option[Tuple[T, U]]":
if other.is_some:
# function typing is correct, we really return an Option[Tuple] but mypy complains that
# other may not have a Value attribute because it do not understand the previous line check.
return Some((self.Some, other.Some)) # type: ignore
return Some((self.Some, other.Some)) # type: ignore[union-attr]

return Empty()

Expand All @@ -394,7 +404,7 @@ def unwrap_empty(self):
def flatten_one(self) -> "Option[T]":
inner: T = self.unwrap()
if isinstance(inner, OptionProtocol):
return inner # type: ignore[return-value]
return cast(Option, inner)
return self

def flatten(self) -> "Option[T]":
Expand All @@ -404,6 +414,12 @@ def flatten(self) -> "Option[T]":
this, inner = (inner, inner.flatten_one())
return this

def transpose(self) -> "Result[Option[T], E]":
if not isinstance(self.Some, ResultProtocol):
raise TypeError("Inner value is not a Result")
value: "ResultProtocol[T, E]" = self.Some
return value.map(Some)

def __bool__(self) -> bool:
return True

Expand Down Expand Up @@ -485,6 +501,9 @@ def flatten_one(self) -> "Option[T]":
def flatten(self) -> "Option[T]":
return self

def transpose(self) -> "Result[Option[T], E]":
return Ok(self)

def __bool__(self) -> bool:
return False

Expand Down Expand Up @@ -681,6 +700,32 @@ def expect_err(self, msg: str) -> E:
"""
... # pragma: no cover

@abstractmethod
def flatten_one(self) -> "Result[T, E]":
"""
Converts from Result[Result[T, E], E] to Result<T, E>, one nested level.
:return: Flattened Result[T, E]
"""
... # pragma: no cover

@abstractmethod
def flatten(self) -> "Result[T, E]":
"""
Converts from Result[Result[T, E], E] to Result<T, E>, any nested level
:return: Flattened Result[T, E]
"""
... # pragma: no cover

@abstractmethod
def transpose(self) -> Option["Result[T, E]"]:
"""
Transposes a Result of an Option into an Option of a Result.
Ok(Empty) will be mapped to Empty. Ok(Some(_)) and Err(_) will be mapped to Some(Ok(_)) and Some(Err(_))
:return: Option[Result[T, E]] as per the mapping above
:raises TypeError if inner value is not an `Option`
"""
... # pragma: no cover

@abstractmethod
def __bool__(self) -> bool:
... # pragma: no cover
Expand Down Expand Up @@ -836,6 +881,23 @@ def unwrap_err(self) -> E:
def expect_err(self, msg: str) -> E:
raise UnwrapException(msg)

def flatten_one(self) -> "Result[T, E]":
if isinstance(self.Ok, ResultProtocol):
return cast(Result, self.unwrap())
return self

def flatten(self) -> "Result[T, E]":
this: Result[T, E] = self
inner: Result[T, E] = self.flatten_one()
while inner is not this:
this, inner = (inner, inner.flatten_one())
return this

def transpose(self) -> Option["Result[T, E]"]:
if not isinstance(self.Ok, OptionProtocol):
raise TypeError("Inner value is not of type Option")
return cast(Option, self.unwrap()).map(Ok)

def __repr__(self):
return f"Ok({self.Ok})"

Expand Down Expand Up @@ -912,6 +974,15 @@ def unwrap_err(self) -> E:
def expect_err(self, msg: str) -> E:
return self.Error

def flatten_one(self) -> "Result[T, E]":
return self

def flatten(self) -> "Result[T, E]":
return self

def transpose(self) -> Option["Result[T, E]"]:
return Some(self)

def __repr__(self):
return f"Err({self.Error})"

Expand Down
10 changes: 8 additions & 2 deletions rusty_results/tests/option/test_option_empty.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,17 @@ def test_empty_unwrap_empty():
Empty().unwrap_empty()


def test_flatten():
def test_flatten_one():
this: Empty = Empty()
assert this.flatten_one() == this


def test_flatten_all():
def test_flatten():
this: Empty = Empty()
assert this.flatten() == this


def test_transpose():
this: Empty = Empty()
assert this.transpose() == Ok(Empty())

16 changes: 16 additions & 0 deletions rusty_results/tests/option/test_option_some.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,19 @@ def test_flatten_one(option: Option, expected_flatten: Option):
)
def test_flatten(option: Option, expected_flatten: Option):
assert option.flatten() == expected_flatten


@pytest.mark.parametrize(
"option, expected_transpose",
[
(Some(Ok(1)), Ok(Some(1))),
(Some(Err(2)), Err(2)),
]
)
def test_transpose(option, expected_transpose):
assert option.transpose() == expected_transpose


def test_transpose_type_error():
with pytest.raises(TypeError):
Some(10).transpose()
15 changes: 15 additions & 0 deletions rusty_results/tests/result/test_result_err.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,18 @@ def test_err_unwrap_err():
def test_err_expect_err():
err: Result[int, int] = Err(0)
assert err.expect_err("foo") == 0


def test_flatten_one():
this: Result = Err(None)
assert this.flatten_one() == this


def test_flatten():
this: Result = Err(None)
assert this.flatten() == this


def test_transpose():
this: Result = Err(None)
assert this.transpose() == Some(Err(None))
44 changes: 44 additions & 0 deletions rusty_results/tests/result/test_result_ok.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,47 @@ def test_ok_expect_err():
with pytest.raises(UnwrapException) as e:
ok.expect_err(exception_msg)
assert str(e.value) == exception_msg


@pytest.mark.parametrize(
"result, expected_flatten",
[
(Ok(1), Ok(1)),
(Ok(Ok(2)), Ok(2)),
(Ok(Ok(Ok(3))), Ok(Ok(3))),
(Ok(Err(None)), Err(None)),
(Ok(Ok(Ok(Ok(Ok(Ok(Err(None))))))), Ok(Ok(Ok(Ok(Ok(Err(None))))))),
]
)
def test_flatten_one(result: Result, expected_flatten: Result):
assert result.flatten_one() == expected_flatten


@pytest.mark.parametrize(
"result, expected_flatten",
[
(Ok(1), Ok(1)),
(Ok(Ok(2)), Ok(2)),
(Ok(Ok(Ok(3))), Ok(3)),
(Ok(Err(None)), Err(None)),
(Ok(Ok(Ok(Ok(Ok(Ok(Err(None))))))), Err(None)),
]
)
def test_flatten(result: Result, expected_flatten: Result):
assert result.flatten() == expected_flatten


@pytest.mark.parametrize(
"result, expected_transpose",
[
(Ok(Some(1)), Some(Ok(1))),
(Ok(Empty()), Empty()),
]
)
def test_transpose(result, expected_transpose):
assert result.transpose() == expected_transpose


def test_transpose_type_error():
with pytest.raises(TypeError):
Ok(10).transpose()

0 comments on commit cc92f86

Please sign in to comment.