Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do notation style #21

Merged
merged 7 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
steps:
- uses: actions/checkout@v2
- name: Install Python 3
Expand Down
20 changes: 20 additions & 0 deletions examples/early_return.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from rusty_results import Option, Some, Empty
from rusty_results import early_return


@early_return
def fail_on_operation() -> Option[int]:
value1 = Some(10)
value2 = Empty()
return Some(~value1 + ~value2)


def success_on_operation() -> Option[int]:
value1 = Some(10)
value2 = Some(10)
return Some(~value1 + ~value2)


if __name__ == "__main__":
print("Success so it return value: ", success_on_operation())
print("Fail so it return Empty: ", fail_on_operation())
2 changes: 1 addition & 1 deletion rusty_results/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .prelude import Option, Some, Empty, Result, Ok, Err
from .exceptions import UnwrapException
from .exceptions import UnwrapException, early_return
23 changes: 23 additions & 0 deletions rusty_results/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
from functools import wraps
from typing import TypeVar


class UnwrapException(Exception):
...


T = TypeVar("T")


class EarlyReturnException(ValueError):
def __init__(self, value: T):
self.value = value
super().__init__(self.value)


def early_return(f):
@wraps(f)
def wrapper(*args, **kwargs):
try:
f(*args, **kwargs)
except EarlyReturnException as e:
return e.value
return wrapper
59 changes: 54 additions & 5 deletions rusty_results/prelude.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from abc import abstractmethod
from dataclasses import dataclass
from typing import cast, TypeVar, Union, Callable, Generic, Iterator, Tuple, Dict, Any
from rusty_results.exceptions import UnwrapException
from rusty_results.exceptions import UnwrapException, EarlyReturnException

try:
from pydantic.fields import ModelField
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -248,7 +249,16 @@ def transpose(self) -> "Result[Option[T], E]":
:return: `Result[Option[T], E]`
:raises TypeError if inner value is not a `Result`
"""
... # pragma: no cover
... # pragma: no cover

@abstractmethod
def early_return(self) -> T:
"""
Access hook for `early_return` wrapper style.
:return: Self if self is Some(T) otherwise
:raises: EarlyReturnException(Empty)
"""
... # pragma: no cover

@abstractmethod
def __bool__(self) -> bool:
Expand All @@ -260,6 +270,14 @@ def __contains__(self, item: T) -> bool:
def __iter__(self):
return self.iter()

def __invert__(self) -> T:
"""
Access hook for `early_return` wrapper style.
:return: Self if self is Some(T) otherwise
:raises: EarlyReturnException(Empty)
"""
return self.early_return()

@classmethod
def __get_validators__(cls):
yield cls.__validate
Expand Down Expand Up @@ -420,6 +438,10 @@ def transpose(self) -> "Result[Option[T], E]":
value: "ResultProtocol[T, E]" = self.Some
return value.map(Some)

def early_return(self) -> T:
# it is safe to unwrap here as we know we are Some
return self.unwrap()

def __bool__(self) -> bool:
return True

Expand Down Expand Up @@ -504,6 +526,9 @@ def flatten(self) -> "Option[T]":
def transpose(self) -> "Result[Option[T], E]":
return Ok(self)

def early_return(self) -> T:
raise EarlyReturnException(self)

def __bool__(self) -> bool:
return False

Expand Down Expand Up @@ -706,15 +731,15 @@ 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
... # 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
... # pragma: no cover

@abstractmethod
def transpose(self) -> Option["Result[T, E]"]:
Expand All @@ -724,7 +749,24 @@ def transpose(self) -> Option["Result[T, E]"]:
:return: Option[Result[T, E]] as per the mapping above
:raises TypeError if inner value is not an `Option`
"""
... # pragma: no cover
... # pragma: no cover

@abstractmethod
def early_return(self) -> T:
"""
Access hook for `early_return` wrapper style.
:return: T if self is Ok(T) otherwise
:raises: EarlyReturnException(Err(e))
"""
... # pragma: no cover

def __invert__(self) -> T:
"""
Access hook for `early_return` wrapper style.
:return: T if self is Ok(T) otherwise
:raises: EarlyReturnException(Err(e))
"""
return self.early_return()

@abstractmethod
def __bool__(self) -> bool:
Expand Down Expand Up @@ -898,6 +940,10 @@ def transpose(self) -> Option["Result[T, E]"]:
raise TypeError("Inner value is not of type Option")
return cast(Option, self.unwrap()).map(Ok)

def early_return(self) -> T:
# safe to unwrap here as we know it is Ok
return self.unwrap()

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

Expand Down Expand Up @@ -983,6 +1029,9 @@ def flatten(self) -> "Result[T, E]":
def transpose(self) -> Option["Result[T, E]"]:
return Some(self)

def early_return(self) -> T:
raise EarlyReturnException(self)

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

Expand Down
Empty file.
11 changes: 11 additions & 0 deletions rusty_results/tests/exceptions/test_early_return.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from rusty_results import early_return, Option, Some, Empty


def test_early_return():
@early_return
def __test_it() -> Option[str]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change the __test_it function to accept a parameter and just use the ~parameter and check if it is empty or Some(10) and we get the 100% coverage back again

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added the pragma to the line that never reaches.

foo: Option = Empty()
_ = ~foo
return Some(10) # pragma: no cover

assert __test_it() == Empty()
5 changes: 5 additions & 0 deletions rusty_results/tests/option/test_option_empty.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,8 @@ def test_transpose():
this: Empty = Empty()
assert this.transpose() == Ok(Empty())


def test_early_return():
with pytest.raises(EarlyReturnException):
this: Empty = Empty()
_ = ~this
5 changes: 5 additions & 0 deletions rusty_results/tests/option/test_option_some.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,8 @@ def test_transpose(option, expected_transpose):
def test_transpose_type_error():
with pytest.raises(TypeError):
Some(10).transpose()


def test_early_return():
value = ~Some(10)
assert value == 10
6 changes: 6 additions & 0 deletions rusty_results/tests/result/test_result_err.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,9 @@ def test_flatten():
def test_transpose():
this: Result = Err(None)
assert this.transpose() == Some(Err(None))


def test_early_return():
err: Result[int, int] = Err(0)
with pytest.raises(EarlyReturnException):
_ = ~err
5 changes: 5 additions & 0 deletions rusty_results/tests/result/test_result_ok.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,8 @@ def test_transpose(result, expected_transpose):
def test_transpose_type_error():
with pytest.raises(TypeError):
Ok(10).transpose()


def test_early_return():
err: Result[int, int] = Ok(0)
assert ~err == 0
Loading