Skip to content

Commit

Permalink
Merge pull request #24 from savannahghi/develop
Browse files Browse the repository at this point in the history
release v1.1.0
  • Loading branch information
kennedykori committed Mar 21, 2024
2 parents 343c7d0 + 185a1a2 commit 1140811
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .releaserc.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
branches:
- main
- name: develop
channel: dev
prerelease: dev
channel: rc
prerelease: rc
plugins:
- '@semantic-release/commit-analyzer'
- - '@semantic-release/release-notes-generator'
Expand Down
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## [1.1.0-rc.1](https://github.com/savannahghi/sghi-commons/compare/v1.0.1...v1.1.0-rc.1) (2024-03-21)


### Features

* **task:** add a task decorator ([#23](https://github.com/savannahghi/sghi-commons/issues/23)) ([2ec9001](https://github.com/savannahghi/sghi-commons/commit/2ec9001a44a97131ecf9bc543dbafbaa30e3a3f3))

## [1.0.1](https://github.com/savannahghi/sghi-commons/compare/v1.0.0...v1.0.1) (2024-03-21)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sghi-commons",
"version": "1.0.1",
"version": "1.1.0-rc.1",
"description": "Collection of utilities and reusable components used throughout our Python projects.",
"directories": {
"doc": "docs"
Expand Down
24 changes: 23 additions & 1 deletion src/sghi/task/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@

from ..disposable import Disposable, ResourceDisposedError
from ..disposable import not_disposed as _nd_factory
from ..utils import ensure_not_none, ensure_not_none_nor_empty, type_fqn
from ..utils import (
ensure_not_none,
ensure_not_none_nor_empty,
ensure_predicate,
type_fqn,
)

if TYPE_CHECKING:
from typing import Self
Expand Down Expand Up @@ -59,6 +64,22 @@ def _callables_to_tasks_as_necessary(
)


def task(f: Callable[[_IT], _OT]) -> Task[_IT, _OT]:
"""Mark/Decorate a ``Callable`` object as a :class:`Task`.
:param f: The callable object to be decorated. The callable *MUST* have at
*MOST* one required argument.
:return: A ``Task`` instance that wraps the given ``Callable`` object.
:raise ValueError: If the given value is ``None`` or not a ``Callable``.
"""
ensure_not_none(f, message="The given callable MUST not be None.")
ensure_predicate(callable(f), message="A callable object is required.")

return _OfCallable(source_callable=f)


# =============================================================================
# EXCEPTIONS
# =============================================================================
Expand Down Expand Up @@ -540,4 +561,5 @@ def execute(self, an_input: _IT) -> _OT:
"consume",
"execute_concurrently",
"pipe",
"task",
]
61 changes: 59 additions & 2 deletions test/sghi/task_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,70 @@
chain,
consume,
pipe,
task,
)
from sghi.utils import ensure_greater_than, future_succeeded

if TYPE_CHECKING:
from collections.abc import Callable


def test_task_decorator_fails_on_non_callable_input_value() -> None:
"""
:func:`task` should raise a :exc:`ValueError` when given a non-callable`
value.
"""

with pytest.raises(ValueError, match="callable object") as exc_info:
task("Not a function") # type: ignore

assert exc_info.value.args[0] == "A callable object is required."


def test_task_decorator_fails_on_a_none_input_value() -> None:
"""
:func:`task` should raise a :exc:`ValueError` when given a ``None`` value.
"""

with pytest.raises(ValueError, match="MUST not be None.") as exc_info:
task(None) # type: ignore

assert exc_info.value.args[0] == "The given callable MUST not be None."


def test_task_decorator_returns_correct_value() -> None:
"""
:func:`task` should return a ``Task`` instance with the same semantics as
the wrapped callable.
"""

add_100: Callable[[int], int] = partial(operator.add, 100)
add_100_task: Task[int, int] = task(add_100)

@task
def int_to_str(value: int) -> str:
return str(value)

assert add_100(10) == add_100_task(10) == 110
assert add_100(-10) == add_100_task(-10) == 90
assert int_to_str(10) == str(10) == "10"
assert int_to_str.execute(3) == str(3) == "3"


def test_task_decorator_returns_expected_value() -> None:
""":func:`task` should return a ``Task`` instance."""

add_100: Callable[[int], int] = partial(operator.add, 100)
add_100_task: Task[int, int] = task(add_100)

@task
def int_to_str(value: int) -> str:
return str(value)

assert isinstance(add_100_task, Task)
assert isinstance(int_to_str, Task)


class TestConsume(TestCase):
"""Tests for the :class:`consume` ``Task``."""

Expand Down Expand Up @@ -423,8 +480,8 @@ def test_tasks_property_return_value_has_tasks_only(self) -> None:
that comprise the ``pipe``. This should be ``Task`` instances
regardless of whether the original callable was a ``Task``.
"""
for task in self._instance.tasks:
assert isinstance(task, Task)
for _task in self._instance.tasks:
assert isinstance(_task, Task)


class TestTask(TestCase):
Expand Down

0 comments on commit 1140811

Please sign in to comment.