Skip to content

Commit c001a9d

Browse files
committed
Remove ways to generate tasks based on returns.
1 parent d76c716 commit c001a9d

File tree

8 files changed

+24
-137
lines changed

8 files changed

+24
-137
lines changed

docs/source/how_to_guides/provisional_nodes_and_task_generators.md

+4
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,7 @@ content to a `.txt` file.
8181

8282
```{literalinclude} ../../../docs_src/how_to_guides/delayed_tasks_task_generator.py
8383
```
84+
85+
```{important}
86+
The generated tasks need to be decoratored with {func}`@task <pytask.task>` to be collected.
87+
```

docs/source/reference_guides/api.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ For example:
136136

137137
```python
138138
@pytask.mark.timeout(10, "slow", method="thread")
139-
def task_function(): ...
139+
def task_function():
140+
...
140141
```
141142

142143
Will create and attach a {class}`Mark <pytask.Mark>` object to the collected
@@ -153,7 +154,8 @@ Example for using multiple custom markers:
153154
```python
154155
@pytask.mark.timeout(10, "slow", method="thread")
155156
@pytask.mark.slow
156-
def task_function(): ...
157+
def task_function():
158+
...
157159
```
158160

159161
### Classes

docs/source/tutorials/repeating_tasks_with_different_inputs.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ the {func}`@task <pytask.task>` decorator to pass keyword arguments to the task.
268268
for id_, kwargs in ID_TO_KWARGS.items():
269269

270270
@task(id=id_, kwargs=kwargs)
271-
def task_create_random_data(seed, produces): ...
271+
def task_create_random_data(seed, produces):
272+
...
272273
```
273274

274275
Writing a function that creates `ID_TO_KWARGS` would be even more pythonic.
@@ -288,7 +289,8 @@ ID_TO_KWARGS = create_parametrization()
288289
for id_, kwargs in ID_TO_KWARGS.items():
289290

290291
@task(id=id_, kwargs=kwargs)
291-
def task_create_random_data(i, produces): ...
292+
def task_create_random_data(i, produces):
293+
...
292294
```
293295

294296
The {doc}`best-practices guide on parametrizations <../how_to_guides/bp_scaling_tasks>`

docs/source/tutorials/selecting_tasks.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ from pytask import task
9191
for i in range(2):
9292

9393
@task
94-
def task_parametrized(i=i): ...
94+
def task_parametrized(i=i):
95+
...
9596
```
9697

9798
To run the task where `i = 1`, run this command.

docs/source/tutorials/skipping_tasks.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ from config import NO_LONG_RUNNING_TASKS
4444
@pytask.mark.skipif(NO_LONG_RUNNING_TASKS, reason="Skip long-running tasks.")
4545
def task_that_takes_really_long_to_run(
4646
path: Path = Path("time_intensive_product.pkl"),
47-
): ...
47+
):
48+
...
4849
```
4950

5051
## Further reading

docs/source/tutorials/write_a_task.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,16 @@ from pytask import task
117117

118118

119119
@task
120-
def create_random_data(): ...
120+
def create_random_data():
121+
...
121122

122123

123124
# The id will be ".../task_data_preparation.py::create_data".
124125

125126

126127
@task(name="create_data")
127-
def create_random_data(): ...
128+
def create_random_data():
129+
...
128130
```
129131

130132
## Customize task module names

src/_pytask/delayed.py

+4-27
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from _pytask.task_utils import parse_collected_tasks_with_task_marker
2424
from _pytask.tree_util import tree_map
2525
from _pytask.tree_util import tree_map_with_path
26-
from _pytask.typing import is_task_function
2726
from _pytask.typing import is_task_generator
2827
from pytask import TaskOutcome
2928

@@ -49,13 +48,8 @@ def _safe_load(node: PNode | PProvisionalNode, task: PTask, is_product: bool) ->
4948
raise NodeLoadError(msg) from e
5049

5150

52-
_ERROR_TASK_GENERATOR_RETURN = """\
53-
Could not collect return of task generator. The return should be a task function or a \
54-
task class but received {} instead."""
55-
56-
5751
@hookimpl
58-
def pytask_execute_task(session: Session, task: PTask) -> None: # noqa: C901, PLR0912
52+
def pytask_execute_task(session: Session, task: PTask) -> None:
5953
"""Execute task generators and collect the tasks."""
6054
if is_task_generator(task):
6155
kwargs = {}
@@ -67,9 +61,7 @@ def pytask_execute_task(session: Session, task: PTask) -> None: # noqa: C901, P
6761
if name in parameters:
6862
kwargs[name] = tree_map(lambda x: _safe_load(x, task, True), value)
6963

70-
out = task.execute(**kwargs)
71-
if inspect.isgenerator(out):
72-
out = list(out)
64+
task.execute(**kwargs)
7365

7466
# Parse tasks created with @task.
7567
name_to_function: Mapping[str, Callable[..., Any] | PTask]
@@ -80,23 +72,8 @@ def pytask_execute_task(session: Session, task: PTask) -> None: # noqa: C901, P
8072
tasks = COLLECTED_TASKS.pop(None)
8173
name_to_function = parse_collected_tasks_with_task_marker(tasks)
8274
else:
83-
# Parse individual tasks.
84-
if is_task_function(out) or isinstance(out, PTask):
85-
out = [out]
86-
# Parse tasks from iterable.
87-
if hasattr(out, "__iter__"):
88-
name_to_function = {}
89-
for obj in out:
90-
if is_task_function(obj):
91-
name_to_function[obj.__name__] = obj
92-
elif isinstance(obj, PTask):
93-
name_to_function[obj.name] = obj
94-
else:
95-
msg = _ERROR_TASK_GENERATOR_RETURN.format(obj)
96-
raise ValueError(msg)
97-
else:
98-
msg = _ERROR_TASK_GENERATOR_RETURN.format(out)
99-
raise ValueError(msg)
75+
msg = "The task generator {task.name!r} did not create any tasks."
76+
raise RuntimeError(msg)
10077

10178
new_reports = []
10279
for name, function in name_to_function.items():

tests/test_execute.py

-102
Original file line numberDiff line numberDiff line change
@@ -1093,108 +1093,6 @@ def task_copy(
10931093
assert tmp_path.joinpath("b-copy.txt").exists()
10941094

10951095

1096-
@pytest.mark.end_to_end()
1097-
def test_delayed_task_generation_with_generator(runner, tmp_path):
1098-
source = """
1099-
from typing_extensions import Annotated
1100-
from pytask import DirectoryNode, task
1101-
from pathlib import Path
1102-
1103-
def task_produces() -> Annotated[None, DirectoryNode(pattern="[ab].txt")]:
1104-
path = Path(__file__).parent
1105-
path.joinpath("a.txt").write_text("Hello, ")
1106-
path.joinpath("b.txt").write_text("World!")
1107-
1108-
@task(after=task_produces, is_generator=True)
1109-
def task_depends(
1110-
paths = DirectoryNode(pattern="[ab].txt")
1111-
) -> ...:
1112-
for path in paths:
1113-
1114-
@task
1115-
def task_copy(
1116-
path: Path = path
1117-
) -> Annotated[str, path.with_name(path.stem + "-copy.txt")]:
1118-
return path.read_text()
1119-
1120-
yield task_copy
1121-
"""
1122-
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
1123-
1124-
result = runner.invoke(cli, [tmp_path.as_posix()])
1125-
assert result.exit_code == ExitCode.OK
1126-
assert "4 Collected tasks" in result.output
1127-
assert "4 Succeeded" in result.output
1128-
assert tmp_path.joinpath("a-copy.txt").exists()
1129-
assert tmp_path.joinpath("b-copy.txt").exists()
1130-
1131-
1132-
@pytest.mark.end_to_end()
1133-
def test_delayed_task_generation_with_single_function(runner, tmp_path):
1134-
source = """
1135-
from typing_extensions import Annotated
1136-
from pytask import DirectoryNode, task
1137-
from pathlib import Path
1138-
1139-
def task_produces() -> Annotated[None, DirectoryNode(pattern="[a].txt")]:
1140-
path = Path(__file__).parent
1141-
path.joinpath("a.txt").write_text("Hello, ")
1142-
1143-
@task(after=task_produces, is_generator=True)
1144-
def task_depends(
1145-
paths = DirectoryNode(pattern="[a].txt")
1146-
) -> ...:
1147-
path = paths[0]
1148-
1149-
def task_copy(
1150-
path: Path = path
1151-
) -> Annotated[str, path.with_name(path.stem + "-copy.txt")]:
1152-
return path.read_text()
1153-
return task_copy
1154-
"""
1155-
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
1156-
1157-
result = runner.invoke(cli, [tmp_path.as_posix()])
1158-
assert result.exit_code == ExitCode.OK
1159-
assert "3 Collected tasks" in result.output
1160-
assert "3 Succeeded" in result.output
1161-
assert tmp_path.joinpath("a-copy.txt").exists()
1162-
1163-
1164-
@pytest.mark.end_to_end()
1165-
def test_delayed_task_generation_with_task_node(runner, tmp_path):
1166-
source = """
1167-
from typing_extensions import Annotated
1168-
from pytask import DirectoryNode, TaskWithoutPath, task, PathNode
1169-
from pathlib import Path
1170-
1171-
def task_produces() -> Annotated[None, DirectoryNode(pattern="[a].txt")]:
1172-
path = Path(__file__).parent
1173-
path.joinpath("a.txt").write_text("Hello, ")
1174-
1175-
@task(after=task_produces, is_generator=True)
1176-
def task_depends(
1177-
paths = DirectoryNode(pattern="[a].txt")
1178-
) -> ...:
1179-
path = paths[0]
1180-
1181-
task_copy = TaskWithoutPath(
1182-
name="task_copy",
1183-
function=lambda path: path.read_text(),
1184-
depends_on={"path": PathNode(path=path)},
1185-
produces={"return": PathNode(path=path.with_name(path.stem + "-copy.txt"))},
1186-
)
1187-
return task_copy
1188-
"""
1189-
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
1190-
1191-
result = runner.invoke(cli, [tmp_path.as_posix()])
1192-
assert result.exit_code == ExitCode.OK
1193-
assert "3 Collected tasks" in result.output
1194-
assert "3 Succeeded" in result.output
1195-
assert tmp_path.joinpath("a-copy.txt").exists()
1196-
1197-
11981096
@pytest.mark.end_to_end()
11991097
def test_gracefully_fail_when_task_generator_raises_error(runner, tmp_path):
12001098
source = """

0 commit comments

Comments
 (0)