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
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ Deprecated
- ``Path.get_content`` is deprecated and will be removed in v5.0.0. Instead use
``Path.read_text`` for text and ``Path.open`` for binary content (`#906
<https://github.com/omni-us/jsonargparse/pull/906>`__).
- ``enable_path`` parameter of :meth:`add_argument
<.ArgumentParser.add_argument>` was deprecated and will be removed in
v5.0.0. Use ``sub_configs`` instead, which is consistent with the
``sub_configs`` parameter of the signature methods,
:meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` etc.
(`#907 <https://github.com/omni-us/jsonargparse/pull/907>`__).
- ``enable_path`` parameter of :class:`.ActionJsonSchema` was deprecated and
will be removed in v5.0.0. Use ``sub_config`` instead (`#907
<https://github.com/omni-us/jsonargparse/pull/907>`__).


v4.48.0 (2026-04-10)
Expand Down
6 changes: 3 additions & 3 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ An argument with a path type can be given ``nargs='+'`` to parse multiple paths.
Thus, from command line you could do ``--files file1 file2``, separated by
space. It might also be desired to parse a list of paths found in a plain text
file or from stdin. For this add the argument with type ``list[<path_type>]``
and ``enable_path=True``. To read from stdin give the special string ``'-'``.
and ``sub_configs=True``. To read from stdin give the special string ``'-'``.
Example:

.. testsetup:: path_list
Expand All @@ -685,7 +685,7 @@ Example:

from jsonargparse.typing import Path_fr

parser.add_argument("--list", type=list[Path_fr], enable_path=True)
parser.add_argument("--list", type=list[Path_fr], sub_configs=True)
cfg = parser.parse_args(["--list", "paths.lst"]) # File with list of paths
cfg = parser.parse_args(["--list", "-"]) # List of paths from stdin

Expand All @@ -706,7 +706,7 @@ automatically created from type hints in signatures, that is with

.. note::

If ``nargs='+'`` and ``enable_path=True`` are set for an argument of type
If ``nargs='+'`` and ``sub_configs=True`` are set for an argument of type
``list[<path_type>]``, each argument will produce a list of paths. This
behavior may not be what you expect.

Expand Down
13 changes: 9 additions & 4 deletions jsonargparse/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,21 @@ def __init__(self, *args, **kwargs) -> None:
self.register("action", "parsers", ActionSubCommands)
self.register("action", "config", ActionConfigFile)

def add_argument(self, *args, enable_path: bool = False, **kwargs):
def add_argument(self, *args, sub_configs: bool = False, **kwargs):
"""Adds an argument to the parser or argument group.

All the arguments from `argparse.ArgumentParser.add_argument
<https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument>`_
are supported. Additionally it accepts:

Args:
enable_path: Whether to try parsing path/subconfig when argument is a complex type.
sub_configs: Whether to try parsing a sub-config when argument is a complex type.
"""
from ._deprecated import add_argument_enable_path_deprecation

deprecated_val = add_argument_enable_path_deprecation(kwargs)
if deprecated_val is not None:
sub_configs = deprecated_val
parser = self.parser if hasattr(self, "parser") else self
if kwargs.get("action") is not None:
if ActionParser._is_valid_action_parser(parser, kwargs["action"]):
Expand All @@ -134,13 +139,13 @@ def add_argument(self, *args, enable_path: bool = False, **kwargs):
if "type" in kwargs:
if is_subclasses_disabled(kwargs["type"]):
nested_key = args[0].lstrip("-")
self.add_class_arguments(kwargs.pop("type"), nested_key, **kwargs)
self.add_class_arguments(kwargs.pop("type"), nested_key, sub_configs=sub_configs, **kwargs)
return find_action(parser, nested_key)
if ActionTypeHint.is_supported_typehint(kwargs["type"]):
args = ActionTypeHint.prepare_add_argument(
args=args,
kwargs=kwargs,
enable_path=enable_path,
enable_path=sub_configs,
container=super(),
logger=self._logger,
)
Expand Down
44 changes: 43 additions & 1 deletion jsonargparse/_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@
"usage_and_exit_error_handler",
]

_message_add_argument_enable_path = """
``enable_path`` parameter of ``add_argument`` was deprecated in v4.49.0 and will be removed in v5.0.0.
Use ``sub_configs`` instead.
"""

_message_action_json_schema_enable_path = """
``enable_path`` parameter of ``ActionJsonSchema`` was deprecated in v4.49.0 and will be removed in v5.0.0.
Use ``sub_config`` instead.
"""


shown_deprecation_warnings: Set[Any] = set()

Expand Down Expand Up @@ -97,6 +107,38 @@ def decorated(*args, **kwargs):
return deprecated_decorator


def add_argument_enable_path_deprecation(kwargs: dict, stacklevel: int = 1) -> Optional[bool]:
"""Handle deprecated ``enable_path`` parameter in ``add_argument``.

If ``enable_path`` is present in kwargs, emit a deprecation warning and
return its value (popping it from kwargs). Returns ``None`` if not present.
"""
if "enable_path" in kwargs:
deprecation_warning(
add_argument_enable_path_deprecation,
_message_add_argument_enable_path,
stacklevel=stacklevel + 1,
)
return kwargs.pop("enable_path")
return None


def action_json_schema_enable_path_deprecation(kwargs: dict, stacklevel: int = 1) -> Optional[bool]:
"""Handle deprecated ``enable_path`` parameter in ``ActionJsonSchema``.

If ``enable_path`` is present in kwargs, emit a deprecation warning and
return its value (popping it from kwargs). Returns ``None`` if not present.
"""
if "enable_path" in kwargs:
deprecation_warning(
action_json_schema_enable_path_deprecation,
_message_action_json_schema_enable_path,
stacklevel=stacklevel + 1,
)
return kwargs.pop("enable_path")
return None


def parse_as_dict_patch():
"""Adds parse_as_dict support to ArgumentParser as a patch.

Expand Down Expand Up @@ -263,7 +305,7 @@ def __call__(self, *args, **kwargs):

@deprecated("""
ActionPathList was deprecated in v4.20.0 and will be removed in v5.0.0. Instead
use as type ``List[<path_type>]`` with ``enable_path=True``.
use as type ``List[<path_type>]`` with ``sub_configs=True``.
""")
class ActionPathList(Action):
"""Action to check and store a list of file paths read from a plain text file or stream."""
Expand Down
17 changes: 11 additions & 6 deletions jsonargparse/_jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,24 @@ class ActionJsonSchema(Action):
"""Action to parse option as JSON validated by a JSON Schema."""

def __init__(
self, schema: Optional[Union[str, dict]] = None, enable_path: bool = True, with_meta: bool = True, **kwargs
self, schema: Optional[Union[str, dict]] = None, sub_config: bool = True, with_meta: bool = True, **kwargs
):
"""Initializer for ActionJsonSchema instance.

Args:
schema: Schema to validate values against.
enable_path: Whether to try to load JSON from path.
sub_config: Whether to try to load JSON from path.
with_meta: Whether to include metadata.

Raises:
ValueError: If a parameter is invalid.
jsonschema.exceptions.SchemaError: If the schema is invalid.
"""
from ._deprecated import action_json_schema_enable_path_deprecation

deprecated_val = action_json_schema_enable_path_deprecation(kwargs)
if deprecated_val is not None:
sub_config = deprecated_val
if schema is not None:
if isinstance(schema, str):
mode = "yaml" if pyyaml_available else "json"
Expand All @@ -45,13 +50,13 @@ def __init__(
jsonvalidator = import_jsonschema("ActionJsonSchema")[1]
jsonvalidator.check_schema(schema)
self._validator = self._extend_jsonvalidator_with_default(jsonvalidator)(schema)
self._enable_path = enable_path
self._sub_config = sub_config
self._with_meta = with_meta
elif "_validator" not in kwargs:
raise ValueError("Expected schema keyword argument.")
else:
self._validator = kwargs.pop("_validator")
self._enable_path = kwargs.pop("_enable_path")
self._sub_config = kwargs.pop("_sub_config")
self._with_meta = kwargs.pop("_with_meta")
super().__init__(**kwargs)

Expand All @@ -63,7 +68,7 @@ def __call__(self, *args, **kwargs):
"""
if len(args) == 0:
kwargs["_validator"] = self._validator
kwargs["_enable_path"] = self._enable_path
kwargs["_sub_config"] = self._sub_config
kwargs["_with_meta"] = self._with_meta
if "help" in kwargs and isinstance(kwargs["help"], str) and "%s" in kwargs["help"]:
import json
Expand All @@ -83,7 +88,7 @@ def _check_type(self, value):
value = [value]
for num, val in enumerate(value):
try:
val, fpath = parse_value_or_config(val, enable_path=self._enable_path)
val, fpath = parse_value_or_config(val, enable_path=self._sub_config)
path_meta = val.pop("__path__") if isinstance(val, dict) and "__path__" in val else None
self._validator.validate(val)
if path_meta is not None:
Expand Down
2 changes: 1 addition & 1 deletion jsonargparse_tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ def test_save_multifile_list(parser, tmp_cwd):
nargs="+",
type=ListItem,
required=True,
enable_path=True,
sub_configs=True,
)

cfg = parser.parse_args([f"--config={main_file_in}"])
Expand Down
38 changes: 38 additions & 0 deletions jsonargparse_tests/test_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from jsonargparse import (
CLI,
ActionJsonnet,
ActionJsonSchema,
ArgumentError,
ArgumentParser,
Namespace,
Expand Down Expand Up @@ -62,6 +63,7 @@
responses_activate,
skip_if_docstring_parser_unavailable,
skip_if_fsspec_unavailable,
skip_if_jsonschema_unavailable,
skip_if_requests_unavailable,
skip_if_responses_unavailable,
)
Expand Down Expand Up @@ -1086,3 +1088,39 @@ def test_compose_dataclasses():
)
assert 2 == len(dataclasses.fields(ComposeAB))
assert {"a": 3, "b": "2"} == dataclasses.asdict(ComposeAB(a=2, b="2")) # pylint: disable=unexpected-keyword-arg


def test_add_argument_enable_path_deprecated(parser, tmp_cwd):
import json

data = {"a": 1}
pathlib.Path("data.yaml").write_text(json.dumps(data))

with catch_warnings(record=True) as w:
parser.add_argument("--data", type=dict, enable_path=True)
assert_deprecation_warn(
w,
message="``enable_path`` parameter of ``add_argument`` was deprecated",
code='parser.add_argument("--data", type=dict, enable_path=True)',
)
cfg = parser.parse_args(["--data=data.yaml"])
path_value = cfg["data"].pop("__path__")
assert "data.yaml" == str(path_value)
assert data == cfg["data"]


@skip_if_jsonschema_unavailable
def test_action_json_schema_enable_path_deprecated(parser):

schema = {"type": "object", "properties": {"x": {"type": "integer"}}}

with catch_warnings(record=True) as w:
action = ActionJsonSchema(schema=schema, enable_path=False)
assert_deprecation_warn(
w,
message="``enable_path`` parameter of ``ActionJsonSchema`` was deprecated",
code="ActionJsonSchema(schema=schema, enable_path=False)",
)
parser.add_argument("--obj", action=action)
cfg = parser.parse_args(['--obj={"x": 1}'])
assert cfg.obj == {"x": 1}
24 changes: 12 additions & 12 deletions jsonargparse_tests/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,14 +553,14 @@ def test_path_fr_default_stdin(parser):
assert defaults.path == Path_fr("-")


# enable_path tests
# sub_configs tests


def test_enable_path_dict(parser, tmp_cwd):
def test_sub_configs_dict(parser, tmp_cwd):
data = {"a": 1, "b": 2, "c": [3, 4]}
pathlib.Path("data.yaml").write_text(json.dumps(data))

parser.add_argument("--data", type=Dict[str, Any], enable_path=True)
parser.add_argument("--data", type=Dict[str, Any], sub_configs=True)
cfg = parser.parse_args(["--data=data.yaml"])
assert "data.yaml" == str(cfg["data"].pop("__path__"))
assert data == cfg["data"]
Expand All @@ -569,17 +569,17 @@ def test_enable_path_dict(parser, tmp_cwd):
ctx.match("Expected a path but does-not-exist.yaml either not accessible or invalid")


def test_enable_path_subclass(parser, tmp_cwd):
def test_sub_configs_subclass(parser, tmp_cwd):
cal = {"class_path": "calendar.Calendar"}
pathlib.Path("cal.yaml").write_text(json.dumps(cal))

parser.add_argument("--cal", type=Calendar, enable_path=True)
parser.add_argument("--cal", type=Calendar, sub_configs=True)
cfg = parser.parse_args(["--cal=cal.yaml"])
init = parser.instantiate(cfg)
assert isinstance(init["cal"], Calendar)


def test_enable_path_list_path_fr(parser, tmp_cwd, mock_stdin, subtests):
def test_sub_configs_list_path_fr(parser, tmp_cwd, mock_stdin, subtests):
tmpdir = tmp_cwd / "subdir"
tmpdir.mkdir()
(tmpdir / "file1").touch()
Expand All @@ -599,13 +599,13 @@ def test_enable_path_list_path_fr(parser, tmp_cwd, mock_stdin, subtests):
parser.add_argument(
"--list",
type=List[Path_fr],
enable_path=True,
sub_configs=True,
)
parser.add_argument(
"--lists",
nargs="+",
type=List[Path_fr],
enable_path=True,
sub_configs=True,
)

with subtests.test("paths list from file"):
Expand Down Expand Up @@ -653,15 +653,15 @@ def test_enable_path_list_path_fr(parser, tmp_cwd, mock_stdin, subtests):

@pytest.mark.parametrize("validate_defaults", [False, True])
@patch_parsing_settings
def test_enable_path_list_path_fr_default_stdin(parser, tmp_cwd, validate_defaults, mock_stdin, subtests):
def test_sub_configs_list_path_fr_default_stdin(parser, tmp_cwd, validate_defaults, mock_stdin, subtests):
set_parsing_settings(validate_defaults=validate_defaults)
(tmp_cwd / "file1").touch()
(tmp_cwd / "file2").touch()

parser.add_argument(
"--list",
type=List[Path_fr],
enable_path=True,
sub_configs=True,
default="-",
)

Expand Down Expand Up @@ -712,11 +712,11 @@ def __init__(self, path: Optional[os.PathLike] = None):
pass


def test_enable_path_optional_pathlike_subclass_parameter(parser, tmp_cwd):
def test_sub_configs_optional_pathlike_subclass_parameter(parser, tmp_cwd):
data_path = pathlib.Path("data.json")
data_path.write_text('{"a": 1}')

parser.add_argument("--data", type=DataOptionalPath, enable_path=True)
parser.add_argument("--data", type=DataOptionalPath, sub_configs=True)

cfg = parser.parse_args([f"--data={__name__}.DataOptionalPath", f"--data.path={data_path}"])
assert cfg.data.class_path == f"{__name__}.DataOptionalPath"
Expand Down
Loading