Skip to content

Commit 524353b

Browse files
authored
Allow task functions to be partialed. (#536)
1 parent c69ab45 commit 524353b

File tree

4 files changed

+46
-3
lines changed

4 files changed

+46
-3
lines changed

docs/source/changes.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
2222
- {pull}`525` enables pytask to work with remote files using universal_pathlib.
2323
- {pull}`528` improves the codecov setup and coverage.
2424
- {pull}`535` reenables and fixes tests with Jupyter.
25+
- {pull}`536` allows partialed functions to be task functions.
2526

2627
## 0.4.4 - 2023-12-04
2728

src/_pytask/task_utils.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Contains utilities related to the ``@pytask.mark.task`` decorator."""
22
from __future__ import annotations
33

4+
import functools
45
import inspect
56
from collections import defaultdict
67
from types import BuiltinFunctionType
@@ -115,7 +116,7 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
115116
path = get_file(unwrapped)
116117

117118
parsed_kwargs = {} if kwargs is None else kwargs
118-
parsed_name = name if isinstance(name, str) else func.__name__
119+
parsed_name = _parse_name(unwrapped, name)
119120
parsed_after = _parse_after(after)
120121

121122
if hasattr(unwrapped, "pytask_meta"):
@@ -148,6 +149,21 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
148149
return wrapper
149150

150151

152+
def _parse_name(func: Callable[..., Any], name: str | None) -> str:
153+
"""Parse name from task function."""
154+
if name:
155+
return name
156+
157+
if isinstance(func, functools.partial):
158+
func = func.func
159+
160+
if hasattr(func, "__name__"):
161+
return func.__name__
162+
163+
msg = "Cannot infer name for task function."
164+
raise NotImplementedError(msg)
165+
166+
151167
def _parse_after(
152168
after: str | Callable[..., Any] | list[Callable[..., Any]] | None,
153169
) -> str | list[Callable[..., Any]]:

tests/test_task.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,9 @@ def func(content):
349349

350350
result = runner.invoke(cli, [tmp_path.as_posix()])
351351

352-
assert result.exit_code == ExitCode.COLLECTION_FAILED
353-
assert "1 Collected errors and tasks" in result.output
352+
assert result.exit_code == ExitCode.OK
353+
assert "1 Succeeded" in result.output
354+
assert tmp_path.joinpath("out.txt").read_text() == "hello"
354355

355356

356357
@pytest.mark.end_to_end()

tests/test_task_utils.py

+25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

33
from contextlib import ExitStack as does_not_raise # noqa: N813
4+
from functools import partial
45
from typing import NamedTuple
56

67
import pytest
78
from _pytask.task_utils import _arg_value_to_id_component
9+
from _pytask.task_utils import _parse_name
810
from _pytask.task_utils import _parse_task_kwargs
911
from attrs import define
1012

@@ -56,3 +58,26 @@ def test_parse_task_kwargs(kwargs, expectation, expected):
5658
with expectation:
5759
result = _parse_task_kwargs(kwargs)
5860
assert result == expected
61+
62+
63+
def task_func(x): # noqa: ARG001 # pragma: no cover
64+
pass
65+
66+
67+
@pytest.mark.unit()
68+
@pytest.mark.parametrize(
69+
("func", "name", "expectation", "expected"),
70+
[
71+
(task_func, None, does_not_raise(), "task_func"),
72+
(task_func, "name", does_not_raise(), "name"),
73+
(partial(task_func, x=1), None, does_not_raise(), "task_func"),
74+
(partial(task_func, x=1), "name", does_not_raise(), "name"),
75+
(lambda x: None, None, does_not_raise(), "<lambda>"), # noqa: ARG005
76+
(partial(lambda x: None, x=1), None, does_not_raise(), "<lambda>"), # noqa: ARG005
77+
(1, None, pytest.raises(NotImplementedError, match="Cannot"), None),
78+
],
79+
)
80+
def test_parse_name(func, name, expectation, expected):
81+
with expectation:
82+
result = _parse_name(func, name)
83+
assert result == expected

0 commit comments

Comments
 (0)