Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
- {pull}`430` updates some parts of the documentation.
- {pull}`431` enables colors for WSL.
- {pull}`432` fixes type checking of `pytask.mark.xxx`.
- {pull}`433` fixes the ids generated for {class}`~pytask.PythonNode`s.

## 0.3.2 - 2023-06-07

Expand Down
34 changes: 27 additions & 7 deletions src/_pytask/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,11 @@ def pytask_collect_task(
if (name.startswith("task_") or has_mark(obj, "task")) and callable(obj):
path_nodes = Path.cwd() if path is None else path.parent
dependencies = parse_dependencies_from_task_function(
session, path_nodes, name, obj
session, path, name, path_nodes, obj
)
products = parse_products_from_task_function(
session, path, name, path_nodes, obj
)
products = parse_products_from_task_function(session, path_nodes, name, obj)

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

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

if isinstance(node, PythonNode):
if not node.name:
suffix = "-" + "-".join(map(str, node_info.path)) if node_info.path else ""
node.name = node_info.arg_name + suffix
prefix = (
node_info.task_path.as_posix() + "::" + node_info.task_name
if node_info.task_path
else node_info.task_name
)
if node.name:
node.name = prefix + "::" + node.name
else:
node.name = prefix + "::" + node_info.arg_name

suffix = "-".join(map(str, node_info.path)) if node_info.path else ""
if suffix:
node.name += "::" + suffix

return node

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

suffix = "-" + "-".join(map(str, node_info.path)) if node_info.path else ""
node_name = node_info.arg_name + suffix
prefix = (
node_info.task_path.as_posix() + "::" + node_info.task_name
if node_info.task_path
else node_info.task_name
)
node_name = prefix + "::" + node_info.arg_name
suffix = "-".join(map(str, node_info.path)) if node_info.path else ""
if suffix:
node_name += "::" + suffix
return PythonNode(value=node, name=node_name)


Expand Down
24 changes: 16 additions & 8 deletions src/_pytask/collect_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import sys
from collections import defaultdict
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING

Expand All @@ -20,6 +21,7 @@
from _pytask.exceptions import ResolvingDependenciesError
from _pytask.mark import select_by_keyword
from _pytask.mark import select_by_mark
from _pytask.node_protocols import PNode
from _pytask.node_protocols import PPathNode
from _pytask.node_protocols import PTask
from _pytask.node_protocols import PTaskWithPath
Expand All @@ -34,7 +36,6 @@


if TYPE_CHECKING:
from pathlib import Path
from typing import NoReturn


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


def _print_collected_tasks(
def _print_collected_tasks( # noqa: PLR0912
dictionary: dict[Path, list[PTaskWithPath]],
show_nodes: bool,
editor_url_scheme: str,
Expand Down Expand Up @@ -199,10 +200,10 @@ def _print_collected_tasks(
)

if show_nodes:
nodes = list(tree_leaves(task.depends_on))
sorted_nodes = sorted(
nodes, key=lambda x: x.name # type: ignore[attr-defined]
nodes: list[PNode] = list(
tree_leaves(task.depends_on) # type: ignore[arg-type]
)
sorted_nodes = sorted(nodes, key=lambda x: x.name)
for node in sorted_nodes:
if isinstance(node, PPathNode):
if node.path.as_posix() in node.name:
Expand All @@ -216,11 +217,18 @@ def _print_collected_tasks(
)
text = Text(reduced_node_name, style=url_style)
else:
text = node.name # type: ignore[attr-defined]
try:
path_part, rest = node.name.split("::", maxsplit=1)
reduced_path = str(
relative_to(Path(path_part), common_ancestor)
)
text = reduced_path + "::" + rest
except Exception: # noqa: BLE001
text = node.name

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

for node in sorted(
for node in sorted( # type: ignore[assignment]
tree_leaves(task.produces),
key=lambda x: getattr(
x, "path", x.name # type: ignore[attr-defined]
Expand All @@ -233,7 +241,7 @@ def _print_collected_tasks(
)
text = Text(reduced_node_name, style=url_style)
else:
text = Text(node.name) # type: ignore[attr-defined]
text = Text(node.name)
task_branch.add(Text.assemble(FILE_ICON, "<Product ", text, ">"))

console.print(tree)
108 changes: 82 additions & 26 deletions src/_pytask/collect_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,30 @@ def produces(objects: PyTree[Any]) -> PyTree[Any]:
return objects


def parse_nodes(
session: Session, path: Path, name: str, obj: Any, parser: Callable[..., Any]
def parse_nodes( # noqa: PLR0913
session: Session,
task_path: Path,
task_name: str,
node_path: Path,
obj: Any,
parser: Callable[..., Any],
) -> Any:
"""Parse nodes from object."""
arg_name = parser.__name__
objects = _extract_nodes_from_function_markers(obj, parser)
nodes = _convert_objects_to_node_dictionary(objects, arg_name)
return tree_map(
lambda x: _collect_decorator_node(
session, path, name, NodeInfo(arg_name, (), x)
session,
node_path,
task_name,
NodeInfo(
arg_name=arg_name,
path=(),
value=x,
task_path=task_path,
task_name=task_name,
),
),
nodes,
)
Expand Down Expand Up @@ -226,7 +240,7 @@ def _merge_dictionaries(list_of_dicts: list[dict[Any, Any]]) -> dict[Any, Any]:


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

if has_mark(obj, "depends_on"):
has_depends_on_decorator = True
nodes = parse_nodes(session, path, name, obj, depends_on)
nodes = parse_nodes(session, task_path, task_name, node_path, obj, depends_on)
dependencies["depends_on"] = nodes

task_kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}
Expand All @@ -248,7 +262,16 @@ def parse_dependencies_from_task_function(
has_depends_on_argument = True
dependencies["depends_on"] = tree_map(
lambda x: _collect_decorator_node(
session, path, name, NodeInfo(arg_name="depends_on", path=(), value=x)
session,
node_path,
task_name,
NodeInfo(
arg_name="depends_on",
path=(),
value=x,
task_path=task_path,
task_name=task_name,
),
),
kwargs["depends_on"],
)
Expand Down Expand Up @@ -284,9 +307,15 @@ def parse_dependencies_from_task_function(
nodes = tree_map_with_path(
lambda p, x: _collect_dependency(
session,
path,
name,
NodeInfo(parameter_name, p, x), # noqa: B023
node_path,
task_name,
NodeInfo(
arg_name=parameter_name, # noqa: B023
path=p,
value=x,
task_path=task_path,
task_name=task_name,
),
),
value,
)
Expand All @@ -297,7 +326,10 @@ def parse_dependencies_from_task_function(
isinstance(x, PythonNode) and not x.hash for x in tree_leaves(nodes)
)
if not isinstance(nodes, PNode) and are_all_nodes_python_nodes_without_hash:
dependencies[parameter_name] = PythonNode(value=value, name=parameter_name)
prefix = task_path.as_posix() + "::" + task_name if task_path else task_name
node_name = prefix + "::" + parameter_name

dependencies[parameter_name] = PythonNode(value=value, name=node_name)
else:
dependencies[parameter_name] = nodes
return dependencies
Expand Down Expand Up @@ -352,7 +384,7 @@ def task_example(produces: Annotated[..., Product]):


def parse_products_from_task_function(
session: Session, path: Path, name: str, obj: Any
session: Session, task_path: Path, task_name: str, node_path: Path, obj: Any
) -> dict[str, Any]:
"""Parse products from task function.

Expand All @@ -372,7 +404,7 @@ def parse_products_from_task_function(
# Parse products from decorators.
if has_mark(obj, "produces"):
has_produces_decorator = True
nodes = parse_nodes(session, path, name, obj, produces)
nodes = parse_nodes(session, task_path, task_name, node_path, obj, produces)
out = {"produces": nodes}

task_kwargs = obj.pytask_meta.kwargs if hasattr(obj, "pytask_meta") else {}
Expand All @@ -388,9 +420,15 @@ def parse_products_from_task_function(
collected_products = tree_map_with_path(
lambda p, x: _collect_product(
session,
path,
name,
NodeInfo(arg_name="produces", path=p, value=x),
node_path,
task_name,
NodeInfo(
arg_name="produces",
path=p,
value=x,
task_path=task_path,
task_name=task_name,
),
is_string_allowed=True,
),
kwargs["produces"],
Expand All @@ -412,8 +450,8 @@ def parse_products_from_task_function(
and parameter_name in parameters_with_node_annot
):
msg = (
f"The value for the parameter {name!r} is defined twice in "
"'@pytask.mark.task(kwargs=...)' and in the type annotation. "
f"The value for the parameter {parameter_name!r} is defined twice "
"in '@pytask.mark.task(kwargs=...)' and in the type annotation. "
"Choose only one option."
)
raise ValueError(msg)
Expand All @@ -425,9 +463,15 @@ def parse_products_from_task_function(
collected_products = tree_map_with_path(
lambda p, x: _collect_product(
session,
path,
name,
NodeInfo(parameter_name, p, x), # noqa: B023
node_path,
task_name,
NodeInfo(
arg_name=parameter_name, # noqa: B023
path=p,
value=x,
task_path=task_path,
task_name=task_name,
),
is_string_allowed=False,
),
value,
Expand All @@ -439,9 +483,15 @@ def parse_products_from_task_function(
collected_products = tree_map_with_path(
lambda p, x: _collect_product(
session,
path,
name,
NodeInfo("return", p, x),
node_path,
task_name,
NodeInfo(
arg_name="return",
path=p,
value=x,
task_path=task_path,
task_name=task_name,
),
is_string_allowed=False,
),
parameters_with_node_annot["return"],
Expand All @@ -454,9 +504,15 @@ def parse_products_from_task_function(
collected_products = tree_map_with_path(
lambda p, x: _collect_product(
session,
path,
name,
NodeInfo("return", p, x),
node_path,
task_name,
NodeInfo(
arg_name="return",
path=p,
value=x,
task_path=task_path,
task_name=task_name,
),
is_string_allowed=False,
),
task_produces,
Expand Down
3 changes: 3 additions & 0 deletions src/_pytask/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from attrs import field

if TYPE_CHECKING:
from pathlib import Path
from _pytask.tree_util import PyTree
from _pytask.mark import Mark

Expand All @@ -33,3 +34,5 @@ class NodeInfo(NamedTuple):
arg_name: str
path: tuple[str | int, ...]
value: Any
task_path: Path
task_name: str
1 change: 0 additions & 1 deletion src/_pytask/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def from_exception(
exc_info: ExceptionInfo,
node: MetaNode | None = None,
) -> CollectionReport:
exc_info = remove_internal_traceback_frames_from_exc_info(exc_info)
return cls(outcome=outcome, node=node, exc_info=exc_info)


Expand Down
Loading