Skip to content

Commit 6fe6785

Browse files
committed
Fix remote UPath collection and node display
1 parent 0a02ef7 commit 6fe6785

5 files changed

Lines changed: 76 additions & 2 deletions

File tree

src/_pytask/collect_command.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from _pytask.node_protocols import PTaskWithPath
3131
from _pytask.outcomes import ExitCode
3232
from _pytask.path import find_common_ancestor
33+
from _pytask.path import is_non_local_path
3334
from _pytask.path import relative_to
3435
from _pytask.pluginmanager import hookimpl
3536
from _pytask.pluginmanager import storage
@@ -125,10 +126,14 @@ def _find_common_ancestor_of_all_nodes(
125126
all_paths.append(task.path)
126127
if show_nodes:
127128
all_paths.extend(
128-
x.path for x in tree_leaves(task.depends_on) if isinstance(x, PPathNode)
129+
x.path
130+
for x in tree_leaves(task.depends_on)
131+
if isinstance(x, PPathNode) and not is_non_local_path(x.path)
129132
)
130133
all_paths.extend(
131-
x.path for x in tree_leaves(task.produces) if isinstance(x, PPathNode)
134+
x.path
135+
for x in tree_leaves(task.produces)
136+
if isinstance(x, PPathNode) and not is_non_local_path(x.path)
132137
)
133138

134139
return find_common_ancestor(*all_paths, *paths)

src/_pytask/path.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from types import ModuleType
1414
from typing import TYPE_CHECKING
1515

16+
from upath import UPath
17+
1618
from _pytask._hashlib import file_digest
1719
from _pytask.cache import Cache
1820

@@ -25,6 +27,7 @@
2527
"find_common_ancestor",
2628
"hash_path",
2729
"import_path",
30+
"is_non_local_path",
2831
"relative_to",
2932
"shorten_path",
3033
]
@@ -56,6 +59,11 @@ def relative_to(path: Path, source: Path, *, include_source: bool = True) -> Pat
5659
return Path(source_name, path.relative_to(source))
5760

5861

62+
def is_non_local_path(path: Path) -> bool:
63+
"""Return whether a path points to a non-local `UPath` resource."""
64+
return isinstance(path, UPath) and bool(path.protocol)
65+
66+
5967
def find_closest_ancestor(
6068
path: Path, potential_ancestors: Sequence[Path]
6169
) -> Path | None:
@@ -432,6 +440,9 @@ def shorten_path(path: Path, paths: Sequence[Path]) -> str:
432440
path from one path in ``session.config["paths"]`` to the node.
433441
434442
"""
443+
if is_non_local_path(path):
444+
return path.as_posix()
445+
435446
ancestor = find_closest_ancestor(path, paths)
436447
if ancestor is None:
437448
try:

tests/test_collect.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pytask import CollectionOutcome
1515
from pytask import ExitCode
1616
from pytask import NodeInfo
17+
from pytask import PickleNode
1718
from pytask import Session
1819
from pytask import Task
1920
from pytask import build
@@ -190,6 +191,29 @@ def test_pytask_collect_node(session, path, node_info, expected):
190191
assert str(result.load()) == str(expected)
191192

192193

194+
def test_pytask_collect_remote_path_node_keeps_uri_name():
195+
upath = pytest.importorskip("upath")
196+
197+
session = Session.from_config(
198+
{"check_casing_of_paths": False, "paths": (Path.cwd(),), "root": Path.cwd()}
199+
)
200+
201+
result = pytask_collect_node(
202+
session,
203+
Path.cwd(),
204+
NodeInfo(
205+
arg_name="path",
206+
path=(),
207+
value=PickleNode(path=upath.UPath("s3://bucket/file.pkl")),
208+
task_path=Path.cwd() / "task_example.py",
209+
task_name="task_example",
210+
),
211+
)
212+
213+
assert isinstance(result, PPathNode)
214+
assert result.name == "s3://bucket/file.pkl"
215+
216+
193217
@pytest.mark.skipif(
194218
sys.platform != "win32", reason="Only works on case-insensitive file systems."
195219
)

tests/test_collect_command.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,31 @@ def test_task_name_is_shortened(runner, tmp_path):
396396
assert "a/b/task_example.py::task_example" not in result.output
397397

398398

399+
def test_collect_task_with_remote_upath_node(runner, tmp_path):
400+
pytest.importorskip("upath")
401+
402+
source = """
403+
from pathlib import Path
404+
from typing import Annotated
405+
406+
from upath import UPath
407+
408+
from pytask import PickleNode
409+
from pytask import Product
410+
411+
def task_example(
412+
data=PickleNode(path=UPath("s3://bucket/in.pkl")),
413+
path: Annotated[Path, Product] = Path("out.txt"),
414+
): ...
415+
"""
416+
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
417+
418+
result = runner.invoke(cli, ["collect", "--nodes", tmp_path.as_posix()])
419+
420+
assert result.exit_code == ExitCode.OK
421+
assert "s3://bucket/in.pkl" in result.output
422+
423+
399424
def test_python_node_is_collected(runner, tmp_path):
400425
source = """
401426
from pytask import Product

tests/test_path.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from _pytask.path import find_closest_ancestor
2121
from _pytask.path import find_common_ancestor
2222
from _pytask.path import relative_to
23+
from _pytask.path import shorten_path
2324
from pytask.path import import_path
2425

2526
if TYPE_CHECKING:
@@ -110,6 +111,14 @@ def test_find_common_ancestor(path_1, path_2, expectation, expected):
110111
assert result == expected
111112

112113

114+
def test_shorten_path_keeps_non_local_uri():
115+
upath = pytest.importorskip("upath")
116+
117+
path = upath.UPath("s3://bucket/file.pkl")
118+
119+
assert shorten_path(path, [Path.cwd()]) == "s3://bucket/file.pkl"
120+
121+
113122
@pytest.mark.skipif(sys.platform != "win32", reason="Only works on Windows.")
114123
@pytest.mark.parametrize(
115124
("path", "existing_paths", "expected"),

0 commit comments

Comments
 (0)