From 666aa38170871760abe756bb3dfcd16c821b2359 Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Fri, 1 May 2026 11:48:34 +0200 Subject: [PATCH 1/2] Deprecate enable_path in favor of sub_config(s) to improve naming consistency --- CHANGELOG.rst | 7 +++++ DOCUMENTATION.rst | 6 ++-- jsonargparse/_core.py | 13 +++++--- jsonargparse/_deprecated.py | 44 ++++++++++++++++++++++++++- jsonargparse/_jsonschema.py | 17 +++++++---- jsonargparse_tests/test_core.py | 2 +- jsonargparse_tests/test_deprecated.py | 37 ++++++++++++++++++++++ jsonargparse_tests/test_paths.py | 24 +++++++-------- 8 files changed, 123 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 66f97d15..e444502a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,6 +47,13 @@ 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 `__). +- ``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.). +- ``enable_path`` parameter of :class:`.ActionJsonSchema` was deprecated and + will be removed in v5.0.0. Use ``sub_config`` instead. v4.48.0 (2026-04-10) diff --git a/DOCUMENTATION.rst b/DOCUMENTATION.rst index ca700f32..1a2f10eb 100644 --- a/DOCUMENTATION.rst +++ b/DOCUMENTATION.rst @@ -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[]`` -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 @@ -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 @@ -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[]``, each argument will produce a list of paths. This behavior may not be what you expect. diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py index 64dacd1d..2a048489 100644 --- a/jsonargparse/_core.py +++ b/jsonargparse/_core.py @@ -116,7 +116,7 @@ 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 @@ -124,8 +124,13 @@ def add_argument(self, *args, enable_path: bool = False, **kwargs): 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"]): @@ -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, ) diff --git a/jsonargparse/_deprecated.py b/jsonargparse/_deprecated.py index 622fd333..155b5278 100644 --- a/jsonargparse/_deprecated.py +++ b/jsonargparse/_deprecated.py @@ -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() @@ -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. @@ -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[]`` with ``enable_path=True``. + use as type ``List[]`` 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.""" diff --git a/jsonargparse/_jsonschema.py b/jsonargparse/_jsonschema.py index 298d92bb..f9765e01 100644 --- a/jsonargparse/_jsonschema.py +++ b/jsonargparse/_jsonschema.py @@ -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" @@ -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) @@ -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 @@ -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: diff --git a/jsonargparse_tests/test_core.py b/jsonargparse_tests/test_core.py index 3f5e1818..9e5ad6c5 100644 --- a/jsonargparse_tests/test_core.py +++ b/jsonargparse_tests/test_core.py @@ -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}"]) diff --git a/jsonargparse_tests/test_deprecated.py b/jsonargparse_tests/test_deprecated.py index 8f8524ca..70c547ee 100644 --- a/jsonargparse_tests/test_deprecated.py +++ b/jsonargparse_tests/test_deprecated.py @@ -19,6 +19,7 @@ from jsonargparse import ( CLI, ActionJsonnet, + ActionJsonSchema, ArgumentError, ArgumentParser, Namespace, @@ -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, ) @@ -1086,3 +1088,38 @@ 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"]) + assert "data.yaml" == str(cfg["data"].pop("__path__")) + 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} diff --git a/jsonargparse_tests/test_paths.py b/jsonargparse_tests/test_paths.py index 79f47105..3be4bf6e 100644 --- a/jsonargparse_tests/test_paths.py +++ b/jsonargparse_tests/test_paths.py @@ -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"] @@ -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() @@ -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"): @@ -653,7 +653,7 @@ 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() @@ -661,7 +661,7 @@ def test_enable_path_list_path_fr_default_stdin(parser, tmp_cwd, validate_defaul parser.add_argument( "--list", type=List[Path_fr], - enable_path=True, + sub_configs=True, default="-", ) @@ -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" From 73a8dc8e6f092f624e36828d3ee8ec88008e3290 Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Fri, 1 May 2026 12:02:55 +0200 Subject: [PATCH 2/2] Fixes --- CHANGELOG.rst | 8 +++++--- jsonargparse_tests/test_deprecated.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e444502a..2f0080cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -50,10 +50,12 @@ Deprecated - ``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.). + ``sub_configs`` parameter of the signature methods, + :meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` etc. + (`#907 `__). - ``enable_path`` parameter of :class:`.ActionJsonSchema` was deprecated and - will be removed in v5.0.0. Use ``sub_config`` instead. + will be removed in v5.0.0. Use ``sub_config`` instead (`#907 + `__). v4.48.0 (2026-04-10) diff --git a/jsonargparse_tests/test_deprecated.py b/jsonargparse_tests/test_deprecated.py index 70c547ee..b61bed0a 100644 --- a/jsonargparse_tests/test_deprecated.py +++ b/jsonargparse_tests/test_deprecated.py @@ -1104,7 +1104,8 @@ def test_add_argument_enable_path_deprecated(parser, tmp_cwd): code='parser.add_argument("--data", type=dict, enable_path=True)', ) cfg = parser.parse_args(["--data=data.yaml"]) - assert "data.yaml" == str(cfg["data"].pop("__path__")) + path_value = cfg["data"].pop("__path__") + assert "data.yaml" == str(path_value) assert data == cfg["data"]