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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ Changed
- Non-parsing actions now have a common base class to ease identification
(`#793 <https://github.com/omni-us/jsonargparse/pull/793>`__).

Deprecated
^^^^^^^^^^
- ``Path.__call__`` is deprecated and will be removed in v5.0.0. Use the
``absolute`` or ``relative`` properties instead (`#794
<https://github.com/omni-us/jsonargparse/pull/794>`__).


v4.42.0 (2025-10-14)
--------------------
Expand Down
9 changes: 9 additions & 0 deletions jsonargparse/_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,11 @@ def path_skip_check_deprecation(stacklevel=2):
-> ``absolute``, ``cwd`` no name change, ``skip_check`` will be removed.
"""

path_call_message = """
Calling Path objects is deprecated and will be removed in v5.0.0. Use the
``absolute`` or ``relative`` properties instead.
"""


class PathDeprecations:
"""Deprecated methods for Path."""
Expand Down Expand Up @@ -493,6 +498,10 @@ def skip_check(self, skip_check):
deprecation_warning("Path attr set", path_immutable_attrs_message)
self._skip_check = skip_check

def __call__(self, absolute: bool = True) -> str:
deprecation_warning("Path.__call__", path_call_message)
return self._absolute if absolute else self._relative


class DebugException(Exception):
pass
Expand Down
10 changes: 1 addition & 9 deletions jsonargparse/_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class PathError(TypeError):


class Path(PathDeprecations):
"""Stores a (possibly relative) path and the corresponding absolute path.
"""Base class for Path types. Stores a (possibly relative) path and the corresponding absolute path.

The absolute path can be obtained without having to remember the working
directory (or parent remote path) from when the object was created.
Expand Down Expand Up @@ -283,14 +283,6 @@ def __eq__(self, other: Any) -> bool:
return str(self) == other
return False

def __call__(self, absolute: bool = True) -> str:
"""Returns the path as a string.

Args:
absolute: If false returns the original path given, otherwise the corresponding absolute path.
"""
return self._absolute if absolute else self._relative

def get_content(self, mode: str = "r") -> str:
"""Returns the contents of the file or the remote path."""
if self._std_io:
Expand Down
34 changes: 24 additions & 10 deletions jsonargparse_tests/test_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
)
from jsonargparse_tests.test_dataclasses import DataClassA
from jsonargparse_tests.test_jsonnet import example_2_jsonnet
from jsonargparse_tests.test_paths import paths # noqa: F401


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -457,29 +458,29 @@ def test_ActionPath(tmp_cwd):
parser.add_argument("--files", nargs="+", action=ActionPath(mode="fr"))

cfg = parser.parse_args(["--cfg", abs_yaml_file])
assert str(tmp_cwd) == os.path.realpath(cfg.dir(absolute=True))
assert abs_yaml_file == os.path.realpath(cfg.cfg[0](absolute=False))
assert abs_yaml_file == os.path.realpath(cfg.cfg[0](absolute=True))
assert rel_yaml_file == cfg.file(absolute=False)
assert abs_yaml_file == os.path.realpath(cfg.file(absolute=True))
assert str(tmp_cwd) == os.path.realpath(cfg.dir.absolute)
assert abs_yaml_file == os.path.realpath(cfg.cfg[0].relative)
assert abs_yaml_file == os.path.realpath(cfg.cfg[0].absolute)
assert rel_yaml_file == cfg.file.relative
assert abs_yaml_file == os.path.realpath(cfg.file.absolute)
pytest.raises(ArgumentError, lambda: parser.parse_args(["--cfg", abs_yaml_file + "~"]))

cfg = parser.parse_args(["--cfg", "file: " + abs_yaml_file + "\ndir: " + str(tmp_cwd) + "\n"])
assert str(tmp_cwd) == os.path.realpath(cfg.dir(absolute=True))
assert str(tmp_cwd) == os.path.realpath(cfg.dir.absolute)
assert cfg.cfg[0] is None
assert abs_yaml_file == os.path.realpath(cfg.file(absolute=True))
assert abs_yaml_file == os.path.realpath(cfg.file.absolute)
pytest.raises(ArgumentError, lambda: parser.parse_args(["--cfg", '{"k":"v"}']))

cfg = parser.parse_args(["--file", abs_yaml_file, "--dir", str(tmp_cwd)])
assert str(tmp_cwd) == os.path.realpath(cfg.dir(absolute=True))
assert abs_yaml_file == os.path.realpath(cfg.file(absolute=True))
assert str(tmp_cwd) == os.path.realpath(cfg.dir.absolute)
assert abs_yaml_file == os.path.realpath(cfg.file.absolute)
pytest.raises(ArgumentError, lambda: parser.parse_args(["--dir", abs_yaml_file]))
pytest.raises(ArgumentError, lambda: parser.parse_args(["--file", str(tmp_cwd)]))

cfg = parser.parse_args(["--files", abs_yaml_file, abs_yaml_file])
assert isinstance(cfg.files, list)
assert 2 == len(cfg.files)
assert abs_yaml_file == os.path.realpath(cfg.files[-1](absolute=True))
assert abs_yaml_file == os.path.realpath(cfg.files[-1].absolute)

pytest.raises(TypeError, lambda: parser.add_argument("--op1", action=ActionPath))
pytest.raises(
Expand Down Expand Up @@ -545,6 +546,19 @@ def test_Path_attr_set(tmp_cwd):
assert "Path objects are not meant to be mutable" in str(w[-1].message)


def test_path_call(paths): # noqa: F811
path = Path(paths.file_rw, "frw")
with catch_warnings(record=True) as w:
assert path(False) == str(paths.file_rw)
assert_deprecation_warn(
w,
message="Calling Path objects is deprecated",
code="assert path(False) == ",
)
assert path(True) == str(paths.tmp_path / paths.file_rw)
assert path() == str(paths.tmp_path / paths.file_rw)


def test_ActionPathList(tmp_cwd):
tmpdir = os.path.join(tmp_cwd, "subdir")
os.mkdir(tmpdir)
Expand Down
21 changes: 9 additions & 12 deletions jsonargparse_tests/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test_path_cwd(paths):

def test_path_empty_mode(paths):
path = Path("does_not_exist", "")
assert path() == str(paths.tmp_path / "does_not_exist")
assert path.absolute == str(paths.tmp_path / "does_not_exist")


def test_path_pathlike(paths):
Expand Down Expand Up @@ -198,9 +198,6 @@ def test_path_invalid_modes(paths):

def test_path_class_hidden_methods(paths):
path = Path(paths.file_rw, "frw")
assert path(False) == str(paths.file_rw)
assert path(True) == str(paths.tmp_path / paths.file_rw)
assert path() == str(paths.tmp_path / paths.file_rw)
assert str(path) == str(paths.file_rw)
assert path.__repr__().startswith("Path_frw(")

Expand All @@ -212,8 +209,8 @@ def test_path_tilde_home(paths):
path = Path(os.path.join("~", paths.file_rw), "frw")
assert str(home) == "~"
assert str(path) == os.path.join("~", paths.file_rw)
assert home() == str(paths.tmp_path)
assert path() == os.path.join(paths.tmp_path, paths.file_rw)
assert home.absolute == str(paths.tmp_path)
assert path.absolute == os.path.join(paths.tmp_path, paths.file_rw)


def test_std_input_path():
Expand Down Expand Up @@ -396,7 +393,7 @@ def test_path_relative_path_context_url():
with path1.relative_path_context() as dir:
assert "http://example.com/nested/path" == dir
path2 = Path("../file2.txt", mode="u")
assert path2() == "http://example.com/nested/file2.txt"
assert path2.absolute == "http://example.com/nested/file2.txt"


@skip_if_fsspec_unavailable
Expand All @@ -421,18 +418,18 @@ def test_relative_path_context_fsspec(tmp_cwd, subtests):
path1 = Path("../file1.txt", mode="fsr")
assert "one" == path1.get_content()
assert str(path1) == "../file1.txt"
assert path1() == "memory://one/two/file1.txt"
assert path1.absolute == "memory://one/two/file1.txt"
assert path1._url_data is not None

with subtests.test("nested fsspec dir"):
with path1.relative_path_context() as dir2:
assert "memory://one/two" == dir2
path2 = Path("four/five/six/../file2.txt", mode="fsc")
assert path2() == "memory://one/two/four/five/file2.txt"
assert path2.absolute == "memory://one/two/four/five/file2.txt"

with subtests.test("non-fsspec path"):
path3 = Path("file3.txt", mode="fc")
assert path3() == str(tmp_cwd / "file3.txt")
assert path3.absolute == str(tmp_cwd / "file3.txt")

with subtests.test("current path dir unset"):
assert _current_path_dir.get() is None
Expand All @@ -444,13 +441,13 @@ def test_relative_path_context_fsspec(tmp_cwd, subtests):
def test_path_fr(file_r):
path = Path_fr(file_r)
assert path == file_r
assert path() == os.path.realpath(file_r)
assert path.absolute == os.path.realpath(file_r)
pytest.raises(TypeError, lambda: Path_fr("does_not_exist"))


def test_path_fc_with_kwargs(tmpdir):
path = Path_fc("some-file.txt", cwd=tmpdir)
assert path() == os.path.join(tmpdir, "some-file.txt")
assert path.absolute == os.path.join(tmpdir, "some-file.txt")


def test_path_fr_already_registered():
Expand Down