Skip to content

Commit

Permalink
feat: added then for elixir implementation and moved utils
Browse files Browse the repository at this point in the history
  • Loading branch information
Jordan-Kowal committed Oct 18, 2024
1 parent b9c5e27 commit 29489d4
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 18 deletions.
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
## AST Implementation

- [ ] Recompute line errors numbers correctly
- [ ] `then` implementation?
- [x] `then` implementation?

## Python implementation

- [x] Handle functions
- [x] Handle lambdas (with `then`)
- [x] Handle class
- [x] Handle methods
- [ ] Handle property
- [ ] ~~Handle property~~
- [ ] Handle func with only unknown args
- [x] Get result easily at the end
- [x] Add `tap` action
Expand Down
4 changes: 2 additions & 2 deletions pipe_operator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .elixir_like import pipes, tap
from .elixir_like import pipes, tap, then
from .python_like import Pipe, PipeEnd, PipeStart, Tap, Then

__all__ = ["pipes", "tap", "Pipe", "PipeEnd", "PipeStart", "Tap", "Then"]
__all__ = ["pipes", "tap", "then", "Pipe", "PipeEnd", "PipeStart", "Tap", "Then"]
4 changes: 2 additions & 2 deletions pipe_operator/elixir_like/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .pipes import pipes, tap
from .pipes import pipes, tap, then

__all__ = ["pipes", "tap"]
__all__ = ["pipes", "tap", "then"]
29 changes: 29 additions & 0 deletions pipe_operator/elixir_like/pipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
PipeTransformer,
)
from pipe_operator.elixir_like.utils import OperatorString
from pipe_operator.shared.utils import is_one_arg_lambda


def pipes(
Expand Down Expand Up @@ -100,6 +101,7 @@ class calls a >> B(...) B(a, ...
>>> >> add(1)
>>> >> _sum(2, 3)
>>> >> (lambda a: a * 2)
>>> >> then(lambda a: a + 1)
>>> >> f"value is {_}"
>>> )
Expand Down Expand Up @@ -154,6 +156,7 @@ def wrapper(func_or_class: Callable) -> Callable:


T = TypeVar("T")
R = TypeVar("R")


def tap(value: T, func_or_class: Callable[[T], Any]) -> T:
Expand All @@ -178,3 +181,29 @@ def tap(value: T, func_or_class: Callable[[T], Any]) -> T:
"""
func_or_class(value)
return value


def then(value: T, f: Callable[[T], R]) -> R:
"""
Elixir-like `then` function to pass a lambda into the pipe.
Simply calls f(value).
Args:
value (T): The value to pass to the function
f (Callable[[T], R]): The function/class to call.
Returns:
R: The result of the function call.
Raises:
TypeError: If the function is not a lambda function.
Examples:
>>> then(42, lambda x: x + 1)
43
"""
if not is_one_arg_lambda(f):
raise TypeError(
"[pipe_operator] `Then` only supports lambda functions. Use `Pipe` instead."
)
return f(value)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from unittest.mock import Mock

from pipe_operator import pipes, tap
from pipe_operator.elixir_like.pipes import then


def add(a: int, b: int) -> int:
Expand Down Expand Up @@ -175,9 +176,10 @@ def test_complex(self) -> None:
>> add(1)
>> _sum(2, 3)
>> (lambda a: a * 2)
>> then(lambda a: a + 1)
>> f"value is {_}"
)
self.assertEqual(op, "value is 140")
self.assertEqual(op, "value is 141")

# ------------------------------
# Ways to apply the decorator
Expand Down Expand Up @@ -241,10 +243,30 @@ def test_with_tap(self) -> None:
)
self.assertEqual(op, 29)

@no_type_check
@pipes
def test_with_then(self) -> None:
op = 0 >> add(10) >> then(lambda a: a**2) >> double
self.assertEqual(op, 200)


class TapTestCase(TestCase):
def test_tap_with_func(self) -> None:
def test_with_func(self) -> None:
mock_func = Mock()
results = tap(4, mock_func)
self.assertEqual(results, 4)
mock_func.assert_called_once_with(4)


class ThenTestCase(TestCase):
def test_with_lambdas(self) -> None:
op = then(4, lambda x: x + 1)
self.assertEqual(op, 5)

def test_should_raise_error_if_not_one_arg_lambda(self) -> None:
with self.assertRaises(TypeError):
then(4, double)
with self.assertRaises(TypeError):
then(4, BasicClass)
with self.assertRaises(TypeError):
then(4, lambda x, y: x + y) # type: ignore
4 changes: 2 additions & 2 deletions pipe_operator/python_like/pipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from typing_extensions import Concatenate, ParamSpec

from pipe_operator.python_like.utils import is_lambda
from pipe_operator.shared.utils import is_lambda, is_one_arg_lambda

TInput = TypeVar("TInput")
TOutput = TypeVar("TOutput")
Expand Down Expand Up @@ -86,7 +86,7 @@ def __init__(self, f: Callable[[ThenInput], ThenOutput]) -> None:
self.raise_if_not_lambda()

def raise_if_not_lambda(self) -> None:
if not is_lambda(self.f):
if not is_one_arg_lambda(self.f):
raise TypeError(
"[pipe_operator] `Then` only supports lambda functions. Use `Pipe` instead."
)
Expand Down
6 changes: 5 additions & 1 deletion pipe_operator/python_like/tests/test_pipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ def test_pipe_does_not_support_lambdas(self) -> None:
with self.assertRaises(TypeError):
_ = PipeStart(3) >> Pipe(lambda x: x + 1) >> PipeEnd()

def test_then_only_supports_lambdas(self) -> None:
def test_then_only_supports_one_arg_lambdas(self) -> None:
with self.assertRaises(TypeError):
_ = PipeStart(3) >> Then(double) >> PipeEnd()
with self.assertRaises(TypeError):
_ = PipeStart(3) >> Then(BasicClass) >> PipeEnd()
with self.assertRaises(TypeError):
_ = PipeStart(3) >> Then(lambda x, y: x + y) >> PipeEnd() # type: ignore

# ------------------------------
# Workflows
Expand Down
6 changes: 0 additions & 6 deletions pipe_operator/python_like/utils.py

This file was deleted.

Empty file.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from unittest import TestCase

from pipe_operator.python_like.utils import is_lambda
from pipe_operator.shared.utils import is_lambda, is_one_arg_lambda


def not_lambda_func(x: int) -> int:
Expand All @@ -16,8 +16,18 @@ class UtilsTestCase(TestCase):
def test_is_lambda(self) -> None:
# Lambda
self.assertTrue(is_lambda(lambda x: x))
self.assertTrue(is_lambda(lambda x, y: x + y))
self.assertTrue(is_lambda(lambda: None))
# Not Lambda
self.assertFalse(is_lambda(not_lambda_func))
self.assertFalse(is_lambda(NotLambdaClass))
self.assertFalse(is_lambda(NotLambdaClass.func))

def test_is_one_arg_lambda(self) -> None:
# Lambda
self.assertTrue(is_one_arg_lambda(lambda x: x))
# Not 1 arg Lambda
self.assertTrue(is_one_arg_lambda(lambda x, y: x + y))
self.assertTrue(is_one_arg_lambda(lambda: None))
self.assertFalse(is_one_arg_lambda(not_lambda_func))
self.assertFalse(is_one_arg_lambda(NotLambdaClass))
12 changes: 12 additions & 0 deletions pipe_operator/shared/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import inspect
import types
from typing import Callable


def is_lambda(f: Callable) -> bool:
return isinstance(f, types.LambdaType) and f.__name__ == "<lambda>"


def is_one_arg_lambda(f: Callable) -> bool:
sig = inspect.signature(f)
return is_lambda(f) and len(sig.parameters) == 1

0 comments on commit 29489d4

Please sign in to comment.