Skip to content

Commit

Permalink
Do notation style (#21)
Browse files Browse the repository at this point in the history
* Added early return implementation, easy to use do-style notation

* Added early return tests

* Use old style typing for compatibility

* Add latest python3 11 and 12 for tests

* Fix comments

* Add decorator test

* Added missing pragmas no cover
  • Loading branch information
danielSanchezQ authored Apr 1, 2024
1 parent fa8d8e2 commit fcace3a
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 6 deletions.
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]:
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

0 comments on commit fcace3a

Please sign in to comment.