diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c79c0733..d58fc81e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -41,6 +41,9 @@ Deprecated - ``Path.__call__`` is deprecated and will be removed in v5.0.0. Use the ``absolute`` or ``relative`` properties instead (`#794 `__). +- ``strip_meta`` is deprecated and will be removed in v5.0.0. Instead use + ``.clone(with_meta=False)`` (`#795 + `__). v4.42.0 (2025-10-14) diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py index 75d1c28e..5852ba71 100644 --- a/jsonargparse/_core.py +++ b/jsonargparse/_core.py @@ -71,10 +71,10 @@ is_meta_key, patch_namespace, recreate_branches, + remove_meta, split_key, split_key_leaf, split_key_root, - strip_meta, ) from ._optionals import ( _get_config_read_mode, @@ -393,7 +393,7 @@ def _parse_common( self.validate(cfg, skip_required=skip_required) if not (with_meta or (with_meta is None and self._default_meta)): - cfg = strip_meta(cfg) + cfg = cfg.clone(with_meta=False) return cfg @@ -793,7 +793,7 @@ def dump( skip_validation = deprecated_skip_check(ArgumentParser.dump, kwargs, skip_validation) check_valid_dump_format(format) - cfg = strip_meta(cfg) + cfg = cfg.clone(with_meta=False) with parser_context(load_value_mode=self.parser_mode): if not skip_validation: @@ -926,7 +926,7 @@ def check_overwrite(path): if not skip_validation: with parser_context(load_value_mode=self.parser_mode): - self.validate(strip_meta(cfg), branch=branch) + self.validate(cfg.clone(with_meta=False), branch=branch) ActionLink.strip_link_target_keys(self, cfg) @@ -937,7 +937,7 @@ def is_path_action(key): def save_path(val): val_path = Path(os.path.basename(val["__path__"].absolute), mode="fc") check_overwrite(val_path) - val_out = strip_meta(val) + val_out = remove_meta(val) if isinstance(val, Namespace): val_out = val_out.as_dict() if "__orig__" in val: @@ -1258,7 +1258,7 @@ def instantiate_classes( order = ActionLink.instantiation_order(self) components = ActionLink.reorder(order, components) - cfg = strip_meta(cfg) + cfg = cfg.clone(with_meta=False) for component in components: ActionLink.apply_instantiation_links(self, cfg, target=component.dest) if isinstance(component, ActionTypeHint): diff --git a/jsonargparse/_deprecated.py b/jsonargparse/_deprecated.py index 77be394d..b58c99ec 100644 --- a/jsonargparse/_deprecated.py +++ b/jsonargparse/_deprecated.py @@ -9,7 +9,7 @@ from importlib import import_module from pathlib import Path from types import ModuleType -from typing import Any, Callable, Dict, Optional, Set +from typing import Any, Callable, Dict, Optional, Set, overload from ._common import Action, null_logger from ._common import LoggerProperty as InternalLoggerProperty @@ -32,6 +32,7 @@ "set_docstring_parse_options", "set_config_read_mode", "set_url_support", + "strip_meta", "usage_and_exit_error_handler", ] @@ -705,6 +706,27 @@ def namespace_to_dict(namespace: Namespace) -> Dict[str, Any]: return namespace.clone().as_dict() +@overload +def strip_meta(cfg: "Namespace") -> "Namespace": ... # pragma: no cover + + +@overload +def strip_meta(cfg: Dict[str, Any]) -> Dict[str, Any]: ... # pragma: no cover + + +@deprecated( + """ + strip_meta was deprecated in v4.43.0 and will be removed in v5.0.0. + Instead use ``.clone(with_meta=False)``. +""" +) +def strip_meta(cfg): + """Removes all metadata keys from a configuration object.""" + from ._namespace import remove_meta + + return remove_meta(cfg) + + class HelpFormatterDeprecations: """Helper class for DefaultHelpFormatter deprecations. Will be removed in v5.0.0.""" diff --git a/jsonargparse/_jsonschema.py b/jsonargparse/_jsonschema.py index a7b9d2f9..9fb6ed6e 100644 --- a/jsonargparse/_jsonschema.py +++ b/jsonargparse/_jsonschema.py @@ -6,7 +6,7 @@ from ._actions import _is_action_value_list from ._common import Action, parser_context from ._loaders_dumpers import get_loader_exceptions, load_value -from ._namespace import strip_meta +from ._namespace import remove_meta from ._optionals import ( get_jsonschema_exceptions, import_jsonschema, @@ -73,7 +73,7 @@ def __call__(self, *args, **kwargs): return class_type(**kwargs) val = self._check_type(args[2]) if not self._with_meta: - val = strip_meta(val) + val = remove_meta(val) setattr(args[1], self.dest, val) return None diff --git a/jsonargparse/_namespace.py b/jsonargparse/_namespace.py index 1ae156be..3498071b 100644 --- a/jsonargparse/_namespace.py +++ b/jsonargparse/_namespace.py @@ -13,13 +13,11 @@ Set, Tuple, Union, - overload, ) __all__ = [ "Namespace", "dict_to_namespace", - "strip_meta", ] @@ -48,23 +46,7 @@ def is_meta_key(key: str) -> bool: return leaf_key in meta_keys -@overload -def strip_meta(cfg: "Namespace") -> "Namespace": ... # pragma: no cover - - -@overload -def strip_meta(cfg: Dict[str, Any]) -> Dict[str, Any]: ... # pragma: no cover - - -def strip_meta(cfg): - """Removes all metadata keys from a configuration object. - - Args: - cfg: The configuration object to strip. - - Returns: - A copy of the configuration object excluding all metadata keys. - """ +def remove_meta(cfg: Union["Namespace", dict]): if cfg: cfg = recreate_branches(cfg, skip_keys=meta_keys) return cfg @@ -275,9 +257,13 @@ def get_sorted_keys(self, branches: bool = True, key_filter: Callable = is_meta_ keys.sort(key=lambda x: -len(split_key(x))) return keys - def clone(self) -> "Namespace": - """Creates an new identical nested namespace.""" - return recreate_branches(self) + def clone(self, with_meta: bool = True) -> "Namespace": + """Creates an new copy of the nested namespace. + + Args: + with_meta: Whether to include metadata keys in the copy. + """ + return recreate_branches(self, skip_keys=None if with_meta else meta_keys) def update( self, value: Union["Namespace", Any], key: Optional[str] = None, only_unset: bool = False diff --git a/jsonargparse_tests/test_actions.py b/jsonargparse_tests/test_actions.py index 236ae0be..bded4df2 100644 --- a/jsonargparse_tests/test_actions.py +++ b/jsonargparse_tests/test_actions.py @@ -12,7 +12,6 @@ ArgumentError, ArgumentParser, Namespace, - strip_meta, ) from jsonargparse_tests.conftest import get_parser_help, json_or_yaml_dump @@ -238,7 +237,7 @@ def test_action_parser_parse_path(composed_parsers): cfg = parser.parse_path(yaml_main) assert "inner2.yaml" == str(cfg.inner2.__path__) assert "inner3.yaml" == str(cfg.inner2.inner3.__path__) - assert expected == strip_meta(cfg).as_dict() + assert expected == cfg.clone(with_meta=False).as_dict() yaml_main2 = yaml_main.parent / "main2.yaml" yaml_main2.write_text(parser.dump(cfg)) diff --git a/jsonargparse_tests/test_core.py b/jsonargparse_tests/test_core.py index 13dad120..1b60fb22 100644 --- a/jsonargparse_tests/test_core.py +++ b/jsonargparse_tests/test_core.py @@ -23,7 +23,6 @@ ArgumentParser, Namespace, set_parsing_settings, - strip_meta, ) from jsonargparse._formatters import get_env_var from jsonargparse._namespace import NSKeyError @@ -667,7 +666,7 @@ def rm_out_files(): with subtests.test("parse_path with metadata"): cfg1 = parser.parse_path(main_file_in, with_meta=True) - assert expected == strip_meta(cfg1) + assert expected == cfg1.clone(with_meta=False) assert str(cfg1.subparser["__path__"]) == "subparser.yaml" if jsonschema_support: assert str(cfg1.schema["__path__"]) == "schema.json" diff --git a/jsonargparse_tests/test_deprecated.py b/jsonargparse_tests/test_deprecated.py index 4b7a1b92..885e7437 100644 --- a/jsonargparse_tests/test_deprecated.py +++ b/jsonargparse_tests/test_deprecated.py @@ -34,6 +34,7 @@ deprecation_warning, namespace_to_dict, shown_deprecation_warnings, + strip_meta, usage_and_exit_error_handler, ) from jsonargparse._formatters import DefaultHelpFormatter @@ -757,6 +758,20 @@ def test_namespace_to_dict(): ) +def test_strip_meta(): + ns = Namespace(x=1, __path__="path") + with catch_warnings(record=True) as w: + result = strip_meta(ns) + assert result == Namespace(x=1) + assert_deprecation_warn( + w, + message="strip_meta was deprecated", + code="result = strip_meta(ns)", + ) + result = strip_meta(ns.as_dict()) + assert result == {"x": 1} + + @pytest.mark.skipif(not ruamel_support, reason="ruamel.yaml package is required") def test_DefaultHelpFormatter_yaml_comments(parser): parser.add_argument("--arg", type=int, help="Description") diff --git a/jsonargparse_tests/test_jsonnet.py b/jsonargparse_tests/test_jsonnet.py index 3f6384ce..663b02ce 100644 --- a/jsonargparse_tests/test_jsonnet.py +++ b/jsonargparse_tests/test_jsonnet.py @@ -11,7 +11,6 @@ ActionJsonSchema, ArgumentError, ArgumentParser, - strip_meta, ) from jsonargparse._optionals import jsonnet_support, pyyaml_available from jsonargparse_tests.conftest import ( @@ -188,19 +187,19 @@ def test_action_jsonnet_save_config_metadata(parser, tmp_path): # parse using saved config and verify result is the same cfg2 = parser.parse_args([f"--cfg={output_config}"]) cfg2.cfg = None - assert strip_meta(cfg) == strip_meta(cfg2) + assert cfg.clone(with_meta=False) == cfg2.clone(with_meta=False) # save the config without metadata and verify it is saved as a single file output_config.unlink() output_jsonnet.unlink() - parser.save(strip_meta(cfg), output_config) + parser.save(cfg.clone(with_meta=False), output_config) assert output_config.is_file() assert not output_jsonnet.is_file() # parse using saved config and verify result is the same cfg3 = parser.parse_args([f"--cfg={output_config}"]) cfg3.cfg = None - assert strip_meta(cfg) == strip_meta(cfg3) + assert cfg.clone(with_meta=False) == cfg3.clone(with_meta=False) @skip_if_jsonschema_unavailable diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py index c936ce05..91aefc80 100644 --- a/jsonargparse_tests/test_signatures.py +++ b/jsonargparse_tests/test_signatures.py @@ -13,7 +13,6 @@ ArgumentError, Namespace, lazy_instance, - strip_meta, ) from jsonargparse._actions import _find_action from jsonargparse._optionals import docstring_parser_support @@ -685,7 +684,7 @@ def test_add_function_group_config_within_config(parser, tmp_cwd): cfg = parser.parse_args([f"--cfg={cfg_path}"]) assert str(cfg.func.__path__) == str(subcfg_path) - assert strip_meta(cfg.func) == Namespace(a1="one", a2=2.0, a3=True, a4=None) + assert cfg.func.clone(with_meta=False) == Namespace(a1="one", a2=2.0, a3=True, a4=None) def func_param_conflict(p1: int, cfg: dict): diff --git a/jsonargparse_tests/test_subcommands.py b/jsonargparse_tests/test_subcommands.py index 6c61f4a9..77c383b5 100644 --- a/jsonargparse_tests/test_subcommands.py +++ b/jsonargparse_tests/test_subcommands.py @@ -12,7 +12,6 @@ ArgumentError, ArgumentParser, Namespace, - strip_meta, ) from jsonargparse_tests.conftest import ( get_parse_args_stderr, @@ -258,7 +257,7 @@ def test_subcommand_required_arg_in_default_config(parser, subparser, tmp_cwd): subcommands.add_subcommand("prepare", subparser) cfg = parser.parse_args([]) assert str(cfg.__default_config__) == "config.yaml" - assert strip_meta(cfg) == Namespace(output="test", prepare=Namespace(media="test"), subcommand="prepare") + assert cfg.clone(with_meta=False) == Namespace(output="test", prepare=Namespace(media="test"), subcommand="prepare") class SubModel: