Skip to content

Commit 6ebe74e

Browse files
authored
Fix subclass defaults incorrectly taken from base class (#743)
1 parent 2632eab commit 6ebe74e

File tree

3 files changed

+36
-4
lines changed

3 files changed

+36
-4
lines changed

CHANGELOG.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,22 @@ The semantic versioning only considers the public API as described in
1212
paths are considered internals and can change in minor and patch releases.
1313

1414

15+
v4.40.2 (2025-07-??)
16+
--------------------
17+
18+
Fixed
19+
^^^^^
20+
- Subclass defaults incorrectly taken from base class (`#743
21+
<https://github.com/omni-us/jsonargparse/pull/743>`__).
22+
23+
1524
v4.40.1 (2025-07-24)
1625
--------------------
1726

1827
Fixed
1928
^^^^^
20-
- ``print_shtab`` incorrectly parsed from environment variable (`#725
21-
<https://github.com/omni-us/jsonargparse/pull/725>`__).
29+
- ``print_shtab`` incorrectly parsed from environment variable (`#726
30+
<https://github.com/omni-us/jsonargparse/pull/726>`__).
2231
- ``adapt_class_type`` used a locally defined `partial_instance` wrapper
2332
function that is not pickleable (`#728
2433
<https://github.com/omni-us/jsonargparse/pull/728>`__).

jsonargparse/_typehints.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,8 @@ def adapt_typehints(
10811081
)
10821082

10831083
try:
1084-
val_class = import_object(resolve_class_path_by_name(typehint, val["class_path"]))
1084+
class_path = resolve_class_path_by_name(typehint, val["class_path"])
1085+
val_class = import_object(class_path)
10851086
if is_instance_or_supports_protocol(val_class, typehint):
10861087
return val_class # importable instance
10871088
if is_protocol(val_class):
@@ -1098,10 +1099,14 @@ def adapt_typehints(
10981099
elif prev_implicit_defaults:
10991100
inner_parser = ActionTypeHint.get_class_parser(typehint, sub_add_kwargs)
11001101
prev_val.init_args = inner_parser.get_defaults()
1102+
if prev_val.class_path != class_path:
1103+
inner_parser = ActionTypeHint.get_class_parser(val_class, sub_add_kwargs)
1104+
for key in inner_parser.get_defaults().keys():
1105+
prev_val.init_args.pop(key, None)
11011106
if not_subclass:
11021107
msg = "implement protocol" if is_protocol(typehint) else "correspond to a subclass of"
11031108
raise_unexpected_value(f"Import path {val['class_path']} does not {msg} {typehint.__name__}")
1104-
val["class_path"] = get_import_path(val_class)
1109+
val["class_path"] = class_path
11051110
val = adapt_class_type(val, serialize, instantiate_classes, sub_add_kwargs, prev_val=prev_val)
11061111
except (ImportError, AttributeError, AssertionError, ArgumentError) as ex:
11071112
class_path = val if isinstance(val, str) else val["class_path"]

jsonargparse_tests/test_subclasses.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@ def test_subclass_basics(parser, type):
5151
assert init["op"] is None
5252

5353

54+
class BaseClassDefault:
55+
def __init__(self, param: str = "base_default"):
56+
self.param = param
57+
58+
59+
class SubClassDefault(BaseClassDefault):
60+
def __init__(self, param: str = "sub_default"):
61+
super().__init__(param=param)
62+
63+
64+
def test_subclass_defaults(parser):
65+
parser.add_subclass_arguments(BaseClassDefault, "cls")
66+
cfg = parser.parse_args(["--cls=BaseClassDefault"])
67+
assert cfg.cls.init_args.param == "base_default"
68+
cfg = parser.parse_args(["--cls=SubClassDefault"])
69+
assert cfg.cls.init_args.param == "sub_default"
70+
71+
5472
def test_subclass_init_args_in_subcommand(parser, subparser):
5573
subparser.add_subclass_arguments(Calendar, "cal", default=lazy_instance(Calendar))
5674
subcommands = parser.add_subcommands()

0 commit comments

Comments
 (0)