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

Feature/transform #60

Merged
merged 6 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
31 changes: 28 additions & 3 deletions meiga/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
TS = TypeVar("TS") # Success Type
TF = TypeVar("TF") # Failure Type
TEF = TypeVar("TEF") # External Failure Type
R = TypeVar("R") # Recast expected type


class Result(Generic[TS, TF]):
Expand All @@ -21,7 +22,12 @@ class Result(Generic[TS, TF]):

__id__ = "__meiga_result_identifier__"
__match_args__ = ("_value_success", "_value_failure")
__slots__ = ("_value_success", "_value_failure", "_is_success")
__slots__ = (
"_value_success",
"_value_failure",
"_is_success",
"_inner_transformer",
)

def __init__(
self,
Expand All @@ -31,6 +37,7 @@ def __init__(
self._value_success = success
self._value_failure = failure
self._assert_values()
self._inner_transformer: Union[Callable[[Result], Any], None] = None
Copy link

Choose a reason for hiding this comment

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

  • maybe use Optional here and in transform(), since it is already imported?
  • * Result[TS, TF]

Copy link
Contributor Author

@acostapazo acostapazo May 12, 2023

Choose a reason for hiding this comment

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

  • The Union[Callable[[Result], Any], None] is equivalent to Optional[Callable[[Result], Any],]. In fact, I think that newer syntax is recommended Callable[[Result], Any] | None. To fully adopt the newer Python syntax while maintaining backwards compatibility, I'm considering adding the from __future__ import annotations statement to meigas's code. This statement enables the use of forward references in annotations, allowing you to refer to classes and types that haven't been defined yet.


def __repr__(self) -> str:
status = "failure"
Expand Down Expand Up @@ -219,11 +226,11 @@ def handle(

return self

def map(self, transform: Callable) -> None:
def map(self, mapper: Callable) -> None:
Copy link

Choose a reason for hiding this comment

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

Pretty sure one cannot use Callable without type arguments in []. Maybe update it to Callable[..., Any] while we're here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know why mypy doesn't warn us about this missing typehint 🤔.

Copy link

Choose a reason for hiding this comment

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

Mypy has pretty tame defaults, to the point that I myself prefer to run it with --strict option (enabling all complaints possible) and then meticulously disabling unwanted options.

Tho forcing this on an unprepared codebase will result in a ton of unexpected red squiggles. Currently counting 33 on meiga/ and 153 on tests/ directories in Meiga repo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ohh thank you for this advice.

"""
Returns a transformed result applying transform function applied to encapsulated value if this instance represents success or failure
"""
new_value = transform(self.value)
new_value = mapper(self.value)
self.set_value(new_value)

def assert_success(
Expand Down Expand Up @@ -255,3 +262,21 @@ def assert_failure(
)

value = property(get_value)

def set_transformer(self, transformer: Callable[["Result"], Any]):
Copy link

Choose a reason for hiding this comment

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

  • *Result[TS, TF]
  • not sure if quoting the type is required here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is required as we're typing inside the Result class (is not defined yet)

Screenshot 2023-05-12 at 16 29 34

I don't know how to ad TS and TF within the quotation. I'll try it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems to work

def set_transformer(self, transformer: Callable[["Result[TS, TF]"], Any]):

"""
Set a Callable transformer to be used with the `transform` method
"""
self._inner_transformer = transformer

def transform(self, expected_type: Union[Type[R], None] = None) -> R: # noqa
"""
Transform the result with set transformer (Use `set_transformer` function to define it)
"""
if not self._inner_transformer:
raise RuntimeError(
"Result object cannot be transformed as no transformer have been set yet. "
"Use result.set_transformer(callable) to add your transformer callable method"
)

return self._inner_transformer(self)
35 changes: 35 additions & 0 deletions tests/unit/test_result_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest

from meiga import Result, Success


def transformer(result: Result):
return result.value


@pytest.mark.unit
def test_should_set_transformer_an_return_transformed_value():
Copy link

Choose a reason for hiding this comment

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

* _and_ instead of _an_? Same with the 2nd test below.

result = Success("Hi")
result.set_transformer(transformer)

recast_result = result.transform()

assert recast_result == "Hi"


@pytest.mark.unit
def test_should_set_transformer_an_return_transformed_value_with_expected_type():
result = Success("Hi")
result.set_transformer(transformer)

recast_result = result.transform(expected_type=str)

assert recast_result == "Hi"


@pytest.mark.unit
def test_should_raise_an_error_when_transform_method_is_not_set():
result = Success("Hi")

with pytest.raises(RuntimeError):
result.transform()