Skip to content

Commit 02bf5e4

Browse files
authored
Fix ids of PythonNodes. (#433)
1 parent 4a77963 commit 02bf5e4

File tree

10 files changed

+180
-57
lines changed

10 files changed

+180
-57
lines changed

docs/source/changes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
5151
- {pull}`430` updates some parts of the documentation.
5252
- {pull}`431` enables colors for WSL.
5353
- {pull}`432` fixes type checking of `pytask.mark.xxx`.
54+
- {pull}`433` fixes the ids generated for {class}`~pytask.PythonNode`s.
5455

5556
## 0.3.2 - 2023-06-07
5657

src/_pytask/collect.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,11 @@ def pytask_collect_task(
231231
if (name.startswith("task_") or has_mark(obj, "task")) and callable(obj):
232232
path_nodes = Path.cwd() if path is None else path.parent
233233
dependencies = parse_dependencies_from_task_function(
234-
session, path_nodes, name, obj
234+
session, path, name, path_nodes, obj
235+
)
236+
products = parse_products_from_task_function(
237+
session, path, name, path_nodes, obj
235238
)
236-
products = parse_products_from_task_function(session, path_nodes, name, obj)
237239

238240
markers = obj.pytask_meta.markers if hasattr(obj, "pytask_meta") else []
239241

@@ -306,9 +308,20 @@ def pytask_collect_node(session: Session, path: Path, node_info: NodeInfo) -> PN
306308
node = node_info.value
307309

308310
if isinstance(node, PythonNode):
309-
if not node.name:
310-
suffix = "-" + "-".join(map(str, node_info.path)) if node_info.path else ""
311-
node.name = node_info.arg_name + suffix
311+
prefix = (
312+
node_info.task_path.as_posix() + "::" + node_info.task_name
313+
if node_info.task_path
314+
else node_info.task_name
315+
)
316+
if node.name:
317+
node.name = prefix + "::" + node.name
318+
else:
319+
node.name = prefix + "::" + node_info.arg_name
320+
321+
suffix = "-".join(map(str, node_info.path)) if node_info.path else ""
322+
if suffix:
323+
node.name += "::" + suffix
324+
312325
return node
313326

314327
if isinstance(node, PPathNode) and not node.path.is_absolute():
@@ -336,8 +349,15 @@ def pytask_collect_node(session: Session, path: Path, node_info: NodeInfo) -> PN
336349
)
337350
return PathNode.from_path(node)
338351

339-
suffix = "-" + "-".join(map(str, node_info.path)) if node_info.path else ""
340-
node_name = node_info.arg_name + suffix
352+
prefix = (
353+
node_info.task_path.as_posix() + "::" + node_info.task_name
354+
if node_info.task_path
355+
else node_info.task_name
356+
)
357+
node_name = prefix + "::" + node_info.arg_name
358+
suffix = "-".join(map(str, node_info.path)) if node_info.path else ""
359+
if suffix:
360+
node_name += "::" + suffix
341361
return PythonNode(value=node, name=node_name)
342362

343363

src/_pytask/collect_command.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import sys
55
from collections import defaultdict
6+
from pathlib import Path
67
from typing import Any
78
from typing import TYPE_CHECKING
89

@@ -20,6 +21,7 @@
2021
from _pytask.exceptions import ResolvingDependenciesError
2122
from _pytask.mark import select_by_keyword
2223
from _pytask.mark import select_by_mark
24+
from _pytask.node_protocols import PNode
2325
from _pytask.node_protocols import PPathNode
2426
from _pytask.node_protocols import PTask
2527
from _pytask.node_protocols import PTaskWithPath
@@ -34,7 +36,6 @@
3436

3537

3638
if TYPE_CHECKING:
37-
from pathlib import Path
3839
from typing import NoReturn
3940

4041

@@ -155,7 +156,7 @@ def _organize_tasks(tasks: list[PTaskWithPath]) -> dict[Path, list[PTaskWithPath
155156
return sorted_dict
156157

157158

158-
def _print_collected_tasks(
159+
def _print_collected_tasks( # noqa: PLR0912
159160
dictionary: dict[Path, list[PTaskWithPath]],
160161
show_nodes: bool,
161162
editor_url_scheme: str,
@@ -199,10 +200,10 @@ def _print_collected_tasks(
199200
)
200201

201202
if show_nodes:
202-
nodes = list(tree_leaves(task.depends_on))
203-
sorted_nodes = sorted(
204-
nodes, key=lambda x: x.name # type: ignore[attr-defined]
203+
nodes: list[PNode] = list(
204+
tree_leaves(task.depends_on) # type: ignore[arg-type]
205205
)
206+
sorted_nodes = sorted(nodes, key=lambda x: x.name)
206207
for node in sorted_nodes:
207208
if isinstance(node, PPathNode):
208209
if node.path.as_posix() in node.name:
@@ -216,11 +217,18 @@ def _print_collected_tasks(
216217
)
217218
text = Text(reduced_node_name, style=url_style)
218219
else:
219-
text = node.name # type: ignore[attr-defined]
220+
try:
221+
path_part, rest = node.name.split("::", maxsplit=1)
222+
reduced_path = str(
223+
relative_to(Path(path_part), common_ancestor)
224+
)
225+
text = reduced_path + "::" + rest
226+
except Exception: # noqa: BLE001
227+
text = node.name
220228

221229
task_branch.add(Text.assemble(FILE_ICON, "<Dependency ", text, ">"))
222230

223-
for node in sorted(
231+
for node in sorted( # type: ignore[assignment]
224232
tree_leaves(task.produces),
225233
key=lambda x: getattr(
226234
x, "path", x.name # type: ignore[attr-defined]
@@ -233,7 +241,7 @@ def _print_collected_tasks(
233241
)
234242
text = Text(reduced_node_name, style=url_style)
235243
else:
236-
text = Text(node.name) # type: ignore[attr-defined]
244+
text = Text(node.name)
237245
task_branch.add(Text.assemble(FILE_ICON, "<Product ", text, ">"))
238246

239247
console.print(tree)

src/_pytask/collect_utils.py

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,30 @@ def produces(objects: PyTree[Any]) -> PyTree[Any]:
7171
return objects
7272

7373

74-
def parse_nodes(
75-
session: Session, path: Path, name: str, obj: Any, parser: Callable[..., Any]
74+
def parse_nodes( # noqa: PLR0913
75+
session: Session,
76+
task_path: Path,
77+
task_name: str,
78+
node_path: Path,
79+
obj: Any,
80+
parser: Callable[..., Any],
7681
) -> Any:
7782
"""Parse nodes from object."""
7883
arg_name = parser.__name__
7984
objects = _extract_nodes_from_function_markers(obj, parser)
8085
nodes = _convert_objects_to_node_dictionary(objects, arg_name)
8186
return tree_map(
8287
lambda x: _collect_decorator_node(
83-
session, path, name, NodeInfo(arg_name, (), x)
88+
session,
89+
node_path,
90+
task_name,
91+
NodeInfo(
92+
arg_name=arg_name,
93+
path=(),
94+
value=x,
95+
task_path=task_path,
96+
task_name=task_name,
97+
),
8498
),
8599
nodes,
86100
)
@@ -226,7 +240,7 @@ def _merge_dictionaries(list_of_dicts: list[dict[Any, Any]]) -> dict[Any, Any]:
226240

227241

228242
def parse_dependencies_from_task_function(
229-
session: Session, path: Path, name: str, obj: Any
243+
session: Session, task_path: Path, task_name: str, node_path: Path, obj: Any
230244
) -> dict[str, Any]:
231245
"""Parse dependencies from task function."""
232246
has_depends_on_decorator = False
@@ -235,7 +249,7 @@ def parse_dependencies_from_task_function(
235249

236250
if has_mark(obj, "depends_on"):
237251
has_depends_on_decorator = True
238-
nodes = parse_nodes(session, path, name, obj, depends_on)
252+
nodes = parse_nodes(session, task_path, task_name, node_path, obj, depends_on)
239253
dependencies["depends_on"] = nodes
240254

241255
task_kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}
@@ -248,7 +262,16 @@ def parse_dependencies_from_task_function(
248262
has_depends_on_argument = True
249263
dependencies["depends_on"] = tree_map(
250264
lambda x: _collect_decorator_node(
251-
session, path, name, NodeInfo(arg_name="depends_on", path=(), value=x)
265+
session,
266+
node_path,
267+
task_name,
268+
NodeInfo(
269+
arg_name="depends_on",
270+
path=(),
271+
value=x,
272+
task_path=task_path,
273+
task_name=task_name,
274+
),
252275
),
253276
kwargs["depends_on"],
254277
)
@@ -284,9 +307,15 @@ def parse_dependencies_from_task_function(
284307
nodes = tree_map_with_path(
285308
lambda p, x: _collect_dependency(
286309
session,
287-
path,
288-
name,
289-
NodeInfo(parameter_name, p, x), # noqa: B023
310+
node_path,
311+
task_name,
312+
NodeInfo(
313+
arg_name=parameter_name, # noqa: B023
314+
path=p,
315+
value=x,
316+
task_path=task_path,
317+
task_name=task_name,
318+
),
290319
),
291320
value,
292321
)
@@ -297,7 +326,10 @@ def parse_dependencies_from_task_function(
297326
isinstance(x, PythonNode) and not x.hash for x in tree_leaves(nodes)
298327
)
299328
if not isinstance(nodes, PNode) and are_all_nodes_python_nodes_without_hash:
300-
dependencies[parameter_name] = PythonNode(value=value, name=parameter_name)
329+
prefix = task_path.as_posix() + "::" + task_name if task_path else task_name
330+
node_name = prefix + "::" + parameter_name
331+
332+
dependencies[parameter_name] = PythonNode(value=value, name=node_name)
301333
else:
302334
dependencies[parameter_name] = nodes
303335
return dependencies
@@ -352,7 +384,7 @@ def task_example(produces: Annotated[..., Product]):
352384

353385

354386
def parse_products_from_task_function(
355-
session: Session, path: Path, name: str, obj: Any
387+
session: Session, task_path: Path, task_name: str, node_path: Path, obj: Any
356388
) -> dict[str, Any]:
357389
"""Parse products from task function.
358390
@@ -372,7 +404,7 @@ def parse_products_from_task_function(
372404
# Parse products from decorators.
373405
if has_mark(obj, "produces"):
374406
has_produces_decorator = True
375-
nodes = parse_nodes(session, path, name, obj, produces)
407+
nodes = parse_nodes(session, task_path, task_name, node_path, obj, produces)
376408
out = {"produces": nodes}
377409

378410
task_kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}
@@ -388,9 +420,15 @@ def parse_products_from_task_function(
388420
collected_products = tree_map_with_path(
389421
lambda p, x: _collect_product(
390422
session,
391-
path,
392-
name,
393-
NodeInfo(arg_name="produces", path=p, value=x),
423+
node_path,
424+
task_name,
425+
NodeInfo(
426+
arg_name="produces",
427+
path=p,
428+
value=x,
429+
task_path=task_path,
430+
task_name=task_name,
431+
),
394432
is_string_allowed=True,
395433
),
396434
kwargs["produces"],
@@ -412,8 +450,8 @@ def parse_products_from_task_function(
412450
and parameter_name in parameters_with_node_annot
413451
):
414452
msg = (
415-
f"The value for the parameter {name!r} is defined twice in "
416-
"'@pytask.mark.task(kwargs=...)' and in the type annotation. "
453+
f"The value for the parameter {parameter_name!r} is defined twice "
454+
"in '@pytask.mark.task(kwargs=...)' and in the type annotation. "
417455
"Choose only one option."
418456
)
419457
raise ValueError(msg)
@@ -425,9 +463,15 @@ def parse_products_from_task_function(
425463
collected_products = tree_map_with_path(
426464
lambda p, x: _collect_product(
427465
session,
428-
path,
429-
name,
430-
NodeInfo(parameter_name, p, x), # noqa: B023
466+
node_path,
467+
task_name,
468+
NodeInfo(
469+
arg_name=parameter_name, # noqa: B023
470+
path=p,
471+
value=x,
472+
task_path=task_path,
473+
task_name=task_name,
474+
),
431475
is_string_allowed=False,
432476
),
433477
value,
@@ -439,9 +483,15 @@ def parse_products_from_task_function(
439483
collected_products = tree_map_with_path(
440484
lambda p, x: _collect_product(
441485
session,
442-
path,
443-
name,
444-
NodeInfo("return", p, x),
486+
node_path,
487+
task_name,
488+
NodeInfo(
489+
arg_name="return",
490+
path=p,
491+
value=x,
492+
task_path=task_path,
493+
task_name=task_name,
494+
),
445495
is_string_allowed=False,
446496
),
447497
parameters_with_node_annot["return"],
@@ -454,9 +504,15 @@ def parse_products_from_task_function(
454504
collected_products = tree_map_with_path(
455505
lambda p, x: _collect_product(
456506
session,
457-
path,
458-
name,
459-
NodeInfo("return", p, x),
507+
node_path,
508+
task_name,
509+
NodeInfo(
510+
arg_name="return",
511+
path=p,
512+
value=x,
513+
task_path=task_path,
514+
task_name=task_name,
515+
),
460516
is_string_allowed=False,
461517
),
462518
task_produces,

src/_pytask/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from attrs import field
1010

1111
if TYPE_CHECKING:
12+
from pathlib import Path
1213
from _pytask.tree_util import PyTree
1314
from _pytask.mark import Mark
1415

@@ -33,3 +34,5 @@ class NodeInfo(NamedTuple):
3334
arg_name: str
3435
path: tuple[str | int, ...]
3536
value: Any
37+
task_path: Path
38+
task_name: str

src/_pytask/report.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def from_exception(
3737
exc_info: ExceptionInfo,
3838
node: MetaNode | None = None,
3939
) -> CollectionReport:
40-
exc_info = remove_internal_traceback_frames_from_exc_info(exc_info)
4140
return cls(outcome=outcome, node=node, exc_info=exc_info)
4241

4342

0 commit comments

Comments
 (0)