Skip to content

Commit eba2be4

Browse files
authored
Deprecate ArgumentParser.merge_config since it is considered internal (#925)
1 parent ae347fb commit eba2be4

10 files changed

Lines changed: 81 additions & 41 deletions

File tree

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Deprecated
5151
- Implicit selection of subcommand when multiple subcommand settings available
5252
is deprecated and will be removed in v5.0.0. Provide an explicit subcommand
5353
instead (`#923 <https://github.com/omni-us/jsonargparse/pull/923>`__).
54+
- ``ArgumentParser.merge_config`` is deprecated and will be removed in v5.0.0.
55+
There is no replacement since it is considered internal (`#925
56+
<https://github.com/omni-us/jsonargparse/pull/925>`__).
5457

5558

5659
v4.49.0 (2026-05-15)

jsonargparse/_actions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
indent_text,
2424
iter_to_set_str,
2525
load_config_path_context,
26+
merge_config,
2627
parse_value_or_config,
2728
)
2829

@@ -127,7 +128,7 @@ def apply_config(parser, cfg, dest, value) -> None:
127128
raise TypeError(f'Parser key "{dest}": {ex_str}') from ex_str
128129
else:
129130
cfg_file = parser.parse_path(value, **kwargs)
130-
cfg_merged = parser.merge_config(cfg_file, cfg)
131+
cfg_merged = merge_config(parser, cfg_file, cfg)
131132
cfg.__dict__.update(cfg_merged.__dict__)
132133
if cfg.get(dest) is get_parsing_setting("unset_sentinel"):
133134
cfg[dest] = []
@@ -246,8 +247,8 @@ def __call__(self, *args, **kwargs):
246247
parser, namespace, value = args[:3]
247248
loaded_value = self._load_config(value, parser)
248249
if isinstance(namespace.get(self.dest), Namespace):
249-
loaded_value = parser.merge_config(
250-
Namespace({self.dest: loaded_value}), Namespace({self.dest: namespace[self.dest]})
250+
loaded_value = merge_config(
251+
parser, Namespace({self.dest: loaded_value}), Namespace({self.dest: namespace[self.dest]})
251252
)[self.dest]
252253
namespace[self.dest] = loaded_value
253254
return None

jsonargparse/_completions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
is_subclass,
2525
type_to_str,
2626
)
27-
from ._util import NoneType, Path, import_object, unique
27+
from ._util import NoneType, Path, import_object, merge_config, unique
2828

2929

3030
def handle_completions(parser):
@@ -80,7 +80,7 @@ def parse_known_args(self, args=None, namespace=None):
8080

8181
def get_argcomplete_namespace(parser, namespace):
8282
namespace.__class__ = __import__("jsonargparse").Namespace
83-
return parser.merge_config(parser.get_defaults(skip_validation=True), namespace).as_flat()
83+
return merge_config(parser, parser.get_defaults(skip_validation=True), namespace).as_flat()
8484

8585

8686
def get_files_completer():

jsonargparse/_core.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
get_private_kwargs,
9595
identity,
9696
load_config_path_context,
97+
merge_config,
9798
return_parser_if_captured,
9899
)
99100

@@ -409,7 +410,7 @@ def _parse_defaults_and_environ(
409410
environ = os.environ
410411
with parser_context(load_value_mode=self.parser_mode):
411412
cfg_env = self._load_env_vars(env=environ, defaults=defaults)
412-
cfg = self.merge_config(cfg_env, cfg)
413+
cfg = merge_config(self, cfg_env, cfg)
413414

414415
return cfg
415416

@@ -457,13 +458,13 @@ def parse_args( # type: ignore[override]
457458
if namespace:
458459
if namespace_as_config:
459460
cfg = self._parse_defaults_and_environ(defaults, env=False)
460-
cfg = self.merge_config(namespace, cfg)
461+
cfg = merge_config(self, namespace, cfg)
461462
if env or (env is None and self._default_env):
462463
with parser_context(load_value_mode=self.parser_mode):
463464
cfg_env = self._load_env_vars(env=os.environ, defaults=defaults)
464-
cfg = self.merge_config(cfg_env, cfg)
465+
cfg = merge_config(self, cfg_env, cfg)
465466
else:
466-
cfg = self.merge_config(namespace, cfg)
467+
cfg = merge_config(self, namespace, cfg)
467468

468469
with parse_kwargs_context({"env": env, "defaults": defaults}):
469470
cfg, unk = self._parse_known_args_internal(args=args, namespace=cfg)
@@ -510,11 +511,11 @@ def parse_object(
510511
try:
511512
cfg = self._parse_defaults_and_environ(defaults, env)
512513
if cfg_base:
513-
cfg = self.merge_config(cfg_base, cfg)
514+
cfg = merge_config(self, cfg_base, cfg)
514515

515516
cfg = self._apply_actions(cfg)
516517
cfg_apply = self._apply_actions(cfg_obj, prev_cfg=cfg)
517-
cfg = self.merge_config(cfg_apply, cfg)
518+
cfg = merge_config(self, cfg_apply, cfg)
518519

519520
parsed_cfg = self._parse_common(
520521
cfg=cfg,
@@ -681,7 +682,7 @@ def parse_string(
681682

682683
if defaults or env:
683684
cfg_base = self._parse_defaults_and_environ(defaults, env)
684-
cfg = self.merge_config(cfg, cfg_base)
685+
cfg = merge_config(self, cfg, cfg_base)
685686

686687
parsed_cfg = self._parse_common(
687688
cfg=cfg,
@@ -1059,7 +1060,7 @@ def get_defaults(self, skip_validation: bool = False, **kwargs) -> Namespace:
10591060
if not default_config_file_content.strip():
10601061
continue
10611062
cfg_file = self._load_config_parser_mode(default_config_file_content, prev_cfg=cfg)
1062-
cfg = self.merge_config(cfg_file, cfg)
1063+
cfg = merge_config(self, cfg_file, cfg)
10631064
try:
10641065
with _ActionPrintConfig.skip_print_config():
10651066
cfg = self._parse_common(
@@ -1367,23 +1368,6 @@ def _apply_actions(
13671368
cfg[action_dest] = value
13681369
return cfg[parent_key] if parent_key else cfg
13691370

1370-
def merge_config(self, cfg_from: Namespace, cfg_to: Namespace) -> Namespace:
1371-
"""Merges the first configuration into the second configuration.
1372-
1373-
Args:
1374-
cfg_from: The configuration from which to merge.
1375-
cfg_to: The configuration into which to merge.
1376-
1377-
Returns:
1378-
A new object with the merged configuration.
1379-
"""
1380-
cfg_from = cfg_from.clone()
1381-
cfg_to = cfg_to.clone()
1382-
with parser_context(parent_parser=self):
1383-
ActionTypeHint.discard_init_args_on_class_path_change(self, cfg_to, cfg_from)
1384-
cfg_to.update(cfg_from)
1385-
return cfg_to
1386-
13871371
def _check_value_key(
13881372
self, action: argparse.Action, value: Any, key: str, cfg: Namespace | None, append: bool = False
13891373
) -> Any:

jsonargparse/_deprecated.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ def patched_parse(
194194

195195
return cfg.as_dict() if self._parse_as_dict and not _skip_validation else cfg
196196

197+
patched_parse.__name__ = method_name
198+
patched_parse.__qualname__ = f"ArgumentParser.{method_name}"
199+
197200
setattr(ArgumentParser, unpatched_method_name, getattr(ArgumentParser, method_name))
198201
setattr(ArgumentParser, method_name, patched_parse)
199202

@@ -750,6 +753,15 @@ def _get_parser_instantiators(self) -> InstantiatorsDictType:
750753
instantiators.update({k: v for k, v in parent_instantiators.items() if k not in instantiators})
751754
return instantiators
752755

756+
@deprecated("""
757+
``ArgumentParser.merge_config`` was deprecated in v4.50.0 and will be
758+
removed in v5.0.0. There is no replacement since this is for internal use.
759+
""")
760+
def merge_config(self, cfg_from: Namespace, cfg_to: Namespace) -> Namespace:
761+
from ._util import merge_config
762+
763+
return merge_config(self, cfg_from, cfg_to)
764+
753765

754766
def deprecated_skip_check(component, kwargs: dict, skip_validation: bool) -> bool:
755767
skip_check = kwargs.pop("skip_check", None)

jsonargparse/_subcommands.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ._deprecated import deprecated_implicit_subcommand
1212
from ._namespace import Namespace, NSKeyError, split_key, split_key_root
1313
from ._type_checking import ActionsContainer, ArgumentParser
14+
from ._util import merge_config
1415

1516
__all__ = ["ActionSubCommands"]
1617

@@ -267,7 +268,7 @@ def handle_subcommands(
267268

268269
# Update all subcommand settings
269270
if subnamespace is not None:
270-
cfg[key] = subparser.merge_config(cfg.get(key, Namespace()), subnamespace)
271+
cfg[key] = merge_config(subparser, cfg.get(key, Namespace()), subnamespace)
271272

272273
# Handle inner subcommands
273274
if subparser._subparsers is not None:

jsonargparse/_util.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
parser_context,
2424
)
2525
from ._loaders_dumpers import json_compact_dump, load_value
26+
from ._namespace import Namespace
2627
from ._optionals import _get_config_read_mode
2728
from ._paths import Path
2829
from ._type_checking import ArgumentParser
@@ -47,6 +48,27 @@ def argument_error(message: str, default_config_file: str | None = None) -> Argu
4748
return ex
4849

4950

51+
def merge_config(parser, source: Namespace, target: Namespace) -> Namespace:
52+
"""Merges the first configuration into the second configuration.
53+
54+
Args:
55+
parser: The parser object.
56+
source: The configuration from which to merge.
57+
target: The configuration into which to merge.
58+
59+
Returns:
60+
A new object with the merged configuration.
61+
"""
62+
from ._typehints import ActionTypeHint
63+
64+
source = source.clone()
65+
target = target.clone()
66+
with parser_context(parent_parser=parser):
67+
ActionTypeHint.discard_init_args_on_class_path_change(parser, target, source)
68+
target.update(source)
69+
return target
70+
71+
5072
def _config_path_id(cfg_path: Path) -> tuple[str, str]:
5173
path_id = cfg_path.absolute
5274
if not (cfg_path.is_url or cfg_path.is_fsspec):

jsonargparse_tests/test_core.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,15 +1158,6 @@ def test_validate_branch(example_parser):
11581158
ctx.match("Expected a <class 'int'>")
11591159

11601160

1161-
def test_merge_config(parser):
1162-
for key in [1, 2, 3]:
1163-
parser.add_argument(f"--op{key}", type=int)
1164-
cfg_from = Namespace(op1=1, op2=None)
1165-
cfg_to = Namespace(op1=None, op2=2, op3=3)
1166-
cfg = parser.merge_config(cfg_from, cfg_to)
1167-
assert cfg == Namespace(op1=1, op2=None, op3=3)
1168-
1169-
11701161
def test_strip_unknown(parser, example_parser):
11711162
for key, default in example_parser.get_defaults().items():
11721163
parser.add_argument("--" + key, default=default)

jsonargparse_tests/test_deprecated.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,3 +1238,18 @@ def test_subcommands_implicit_in_default_config_files(parser, tmp_cwd):
12381238
assert cfg.sub == "sub1"
12391239
assert cfg.sub1 == Namespace(sub1val=2)
12401240
assert "sub2" not in cfg
1241+
1242+
1243+
def test_deprecated_merge_config(parser):
1244+
for key in [1, 2, 3]:
1245+
parser.add_argument(f"--op{key}", type=int)
1246+
cfg_from = Namespace(op1=1, op2=None)
1247+
cfg_to = Namespace(op1=None, op2=2, op3=3)
1248+
with catch_warnings(record=True) as w:
1249+
cfg = parser.merge_config(cfg_from, cfg_to)
1250+
assert cfg == Namespace(op1=1, op2=None, op3=3)
1251+
assert_deprecation_warn(
1252+
w,
1253+
message="``ArgumentParser.merge_config`` was deprecated",
1254+
code="cfg = parser.merge_config(cfg_from, cfg_to)",
1255+
)

jsonargparse_tests/test_util.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,23 @@
1717
CaptureParserException,
1818
get_import_path,
1919
import_object,
20+
merge_config,
2021
object_path_serializer,
2122
register_unresolvable_import_paths,
2223
unique,
2324
)
2425
from jsonargparse_tests.conftest import capture_logs
2526

27+
28+
def test_merge_config(parser):
29+
for key in [1, 2, 3]:
30+
parser.add_argument(f"--op{key}", type=int)
31+
cfg_from = Namespace(op1=1, op2=None)
32+
cfg_to = Namespace(op1=None, op2=2, op3=3)
33+
cfg = merge_config(parser, cfg_from, cfg_to)
34+
assert cfg == Namespace(op1=1, op2=None, op3=3)
35+
36+
2637
# logger property tests
2738

2839

@@ -130,7 +141,7 @@ def test_import_object_invalid():
130141

131142
def test_get_import_path():
132143
assert get_import_path(ArgumentParser) == "jsonargparse.ArgumentParser"
133-
assert get_import_path(ArgumentParser.merge_config) == "jsonargparse.ArgumentParser.merge_config"
144+
assert get_import_path(ArgumentParser.parse_args) == "jsonargparse.ArgumentParser.parse_args"
134145
from email.mime.base import MIMEBase
135146

136147
assert get_import_path(MIMEBase) == "email.mime.base.MIMEBase"

0 commit comments

Comments
 (0)