Skip to content

Commit ae31b65

Browse files
authored
Parse dependencies from all args if depends_on is not used. (#384)
1 parent a99e54a commit ae31b65

18 files changed

+437
-141
lines changed

docs/source/changes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
88
## 0.4.0 - 2023-xx-xx
99

1010
- {pull}`323` remove Python 3.7 support and use a new Github action to provide mamba.
11+
- {pull}`384` allows to parse dependencies from every function argument if `depends_on`
12+
is not present.
1113
- {pull}`387` replaces pony with sqlalchemy.
1214
- {pull}`391` removes `@pytask.mark.parametrize`.
1315

docs/source/tutorials/defining_dependencies_products.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ To ensure pytask executes all tasks in the correct order, define which dependenc
44
required and which products are produced by a task.
55

66
:::{important}
7-
If you do not specify dependencies and products as explained below, pytask will not be able
8-
to build a graph, a {term}`DAG`, and will not be able to execute all tasks in the
7+
If you do not specify dependencies and products as explained below, pytask will not be
8+
able to build a graph, a {term}`DAG`, and will not be able to execute all tasks in the
99
project correctly!
1010
:::
1111

@@ -19,15 +19,16 @@ def task_create_random_data(produces):
1919
...
2020
```
2121

22-
The {func}`@pytask.mark.produces <pytask.mark.produces>` marker attaches a
23-
product to a task which is a {class}`pathlib.Path` to file. After the task has finished,
24-
pytask will check whether the file exists.
22+
The {func}`@pytask.mark.produces <pytask.mark.produces>` marker attaches a product to a
23+
task which is a {class}`pathlib.Path` to file. After the task has finished, pytask will
24+
check whether the file exists.
2525

26-
Optionally, you can use `produces` as an argument of the task function and get access to
27-
the same path inside the task function.
26+
Add `produces` as an argument of the task function to get access to the same path inside
27+
the task function.
2828

2929
:::{tip}
30-
If you do not know about {mod}`pathlib` check out [^id3] and [^id4]. The module is beneficial for handling paths conveniently and across platforms.
30+
If you do not know about {mod}`pathlib` check out [^id3] and [^id4]. The module is
31+
beneficial for handling paths conveniently and across platforms.
3132
:::
3233

3334
## Dependencies
@@ -44,7 +45,7 @@ def task_plot_data(depends_on, produces):
4445
...
4546
```
4647

47-
Use `depends_on` as a function argument to work with the dependency path and, for
48+
Add `depends_on` as a function argument to work with the path of the dependency and, for
4849
example, load the data.
4950

5051
## Conversion
@@ -61,9 +62,6 @@ def task_create_random_data(produces):
6162
...
6263
```
6364

64-
If you use `depends_on` or `produces` as arguments for the task function, you will have
65-
access to the paths of the targets as {class}`pathlib.Path`.
66-
6765
## Multiple dependencies and products
6866

6967
The easiest way to attach multiple dependencies or products to a task is to pass a
@@ -108,7 +106,9 @@ Why does pytask recommend dictionaries and convert lists, tuples, or other
108106
iterators to dictionaries? First, dictionaries with positions as keys behave very
109107
similarly to lists.
110108

111-
Secondly, dictionaries use keys instead of positions that are more verbose and descriptive and do not assume a fixed ordering. Both attributes are especially desirable in complex projects.
109+
Secondly, dictionaries use keys instead of positions that are more verbose and
110+
descriptive and do not assume a fixed ordering. Both attributes are especially desirable
111+
in complex projects.
112112

113113
## Multiple decorators
114114

src/_pytask/collect.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from typing import Iterable
1313

1414
from _pytask.collect_utils import depends_on
15+
from _pytask.collect_utils import parse_dependencies_from_task_function
1516
from _pytask.collect_utils import parse_nodes
17+
from _pytask.collect_utils import parse_products_from_task_function
1618
from _pytask.collect_utils import produces
1719
from _pytask.config import hookimpl
1820
from _pytask.config import IS_FILE_SYSTEM_CASE_SENSITIVE
@@ -22,6 +24,7 @@
2224
from _pytask.exceptions import CollectionError
2325
from _pytask.mark_utils import has_mark
2426
from _pytask.nodes import FilePathNode
27+
from _pytask.nodes import PythonNode
2528
from _pytask.nodes import Task
2629
from _pytask.outcomes import CollectionOutcome
2730
from _pytask.outcomes import count_outcomes
@@ -167,11 +170,20 @@ def pytask_collect_task(
167170
168171
"""
169172
if (name.startswith("task_") or has_mark(obj, "task")) and callable(obj):
170-
dependencies = parse_nodes(session, path, name, obj, depends_on)
171-
products = parse_nodes(session, path, name, obj, produces)
173+
if has_mark(obj, "depends_on"):
174+
nodes = parse_nodes(session, path, name, obj, depends_on)
175+
dependencies = {"depends_on": nodes}
176+
else:
177+
dependencies = parse_dependencies_from_task_function(
178+
session, path, name, obj
179+
)
180+
181+
if has_mark(obj, "produces"):
182+
products = parse_nodes(session, path, name, obj, produces)
183+
else:
184+
products = parse_products_from_task_function(session, path, name, obj)
172185

173186
markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else []
174-
kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}
175187

176188
# Get the underlying function to avoid having different states of the function,
177189
# e.g. due to pytask_meta, in different layers of the wrapping.
@@ -184,7 +196,6 @@ def pytask_collect_task(
184196
depends_on=dependencies,
185197
produces=products,
186198
markers=markers,
187-
kwargs=kwargs,
188199
)
189200
return None
190201

@@ -205,7 +216,7 @@ def pytask_collect_task(
205216
@hookimpl(trylast=True)
206217
def pytask_collect_node(
207218
session: Session, path: Path, node: str | Path
208-
) -> FilePathNode | None:
219+
) -> FilePathNode | PythonNode:
209220
"""Collect a node of a task as a :class:`pytask.nodes.FilePathNode`.
210221
211222
Strings are assumed to be paths. This might be a strict assumption, but since this
@@ -226,8 +237,6 @@ def pytask_collect_node(
226237
handled by this function.
227238
228239
"""
229-
if isinstance(node, str):
230-
node = Path(node)
231240
if isinstance(node, Path):
232241
if not node.is_absolute():
233242
node = path.parent.joinpath(node)
@@ -246,7 +255,7 @@ def pytask_collect_node(
246255
raise ValueError(_TEMPLATE_ERROR.format(node, case_sensitive_path))
247256

248257
return FilePathNode.from_path(node)
249-
return None
258+
return PythonNode(value=node)
250259

251260

252261
def _not_ignored_paths(

src/_pytask/collect_command.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from _pytask.exceptions import ResolvingDependenciesError
2121
from _pytask.mark import select_by_keyword
2222
from _pytask.mark import select_by_mark
23+
from _pytask.nodes import FilePathNode
2324
from _pytask.outcomes import ExitCode
2425
from _pytask.path import find_common_ancestor
2526
from _pytask.path import relative_to
@@ -123,7 +124,11 @@ def _find_common_ancestor_of_all_nodes(
123124
for task in tasks:
124125
all_paths.append(task.path)
125126
if show_nodes:
126-
all_paths.extend(x.path for x in tree_just_flatten(task.depends_on))
127+
all_paths.extend(
128+
x.path
129+
for x in tree_just_flatten(task.depends_on)
130+
if isinstance(x, FilePathNode)
131+
)
127132
all_paths.extend(x.path for x in tree_just_flatten(task.produces))
128133

129134
common_ancestor = find_common_ancestor(*all_paths, *paths)
@@ -160,14 +165,14 @@ def _print_collected_tasks(
160165
161166
Parameters
162167
----------
163-
dictionary : Dict[Path, List["Task"]]
168+
dictionary
164169
A dictionary with path on the first level, tasks on the second, dependencies and
165170
products on the third.
166-
show_nodes : bool
171+
show_nodes
167172
Indicator for whether dependencies and products should be displayed.
168-
editor_url_scheme : str
173+
editor_url_scheme
169174
The scheme to create an url.
170-
common_ancestor : Path
175+
common_ancestor
171176
The path common to all tasks and nodes.
172177
173178
"""
@@ -197,9 +202,13 @@ def _print_collected_tasks(
197202
)
198203

199204
if show_nodes:
200-
for node in sorted(
201-
tree_just_flatten(task.depends_on), key=lambda x: x.path
202-
):
205+
file_path_nodes = [
206+
i
207+
for i in tree_just_flatten(task.depends_on)
208+
if isinstance(i, FilePathNode)
209+
]
210+
sorted_nodes = sorted(file_path_nodes, key=lambda x: x.path)
211+
for node in sorted_nodes:
203212
reduced_node_name = relative_to(node.path, common_ancestor)
204213
url_style = create_url_style_for_path(node.path, editor_url_scheme)
205214
task_branch.add(

0 commit comments

Comments
 (0)