Skip to content

Commit 703d66d

Browse files
committed
More tests for full coverage
1 parent a5d8510 commit 703d66d

6 files changed

Lines changed: 97 additions & 29 deletions

File tree

DOCUMENTATION.rst

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2335,9 +2335,9 @@ in ``subclasses_disabled``; in this case, the effect is to unregister it. By
23352335
default, the following disable functions are registered: ``is_pure_dataclass``,
23362336
``is_pydantic_model``, ``is_attrs_class``, and ``is_final_class``.
23372337

2338-
Since ``subclasses_enabled`` takes precedence, it is possible to keep subclass
2339-
support disabled for dataclasses, but enable enable it for a specific dataclass
2340-
as follows:
2338+
Some examples. Since ``subclasses_enabled`` takes precedence, it is possible to
2339+
keep subclass support disabled for dataclasses, but enable enable it for a
2340+
specific dataclass as follows:
23412341

23422342
.. testsetup:: enable_disable_subclasses
23432343

@@ -2358,12 +2358,21 @@ as follows:
23582358

23592359
set_parsing_settings(subclasses_enabled=[DataClassBaseType])
23602360

2361-
To enable subclass support for all dataclasses and pydantic models, the
2362-
following can be done:
2361+
To enable subclass support for all pydantic models, the following can be done:
23632362

23642363
.. testcode:: enable_disable_subclasses
23652364

2366-
set_parsing_settings(subclasses_enabled=["is_pure_dataclass", "is_pydantic_model"])
2365+
set_parsing_settings(subclasses_enabled=["is_pydantic_model"])
2366+
2367+
To enable subclass support for all dataclasses, but have it disabled for a
2368+
specific dataclass, the following can be done:
2369+
2370+
.. testcode:: enable_disable_subclasses
2371+
2372+
set_parsing_settings(
2373+
subclasses_enabled=["is_pure_dataclass"],
2374+
subclasses_disabled=[DataClassBaseType],
2375+
)
23672376

23682377

23692378
.. _argument-linking:

jsonargparse/_common.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,9 @@ def is_subclasses_disabled(cls) -> bool:
319319
return False
320320
subclass_disabled = any(selector(cls) for selector in subclasses_disabled_selectors.values())
321321
if not subclass_disabled:
322-
subclass_disabled = any(issubclass(cls, disabled_type) for disabled_type in subclasses_disabled_types)
322+
subclass_disabled = any(issubclass(cls, disable_type) for disable_type in subclasses_disabled_types)
323323
if subclass_disabled:
324-
subclass_disabled = not any(issubclass(cls, enabled_type) for enabled_type in subclasses_enabled_types)
324+
subclass_disabled = not any(issubclass(cls, enable_type) for enable_type in subclasses_enabled_types)
325325
return subclass_disabled
326326

327327

@@ -349,7 +349,7 @@ def subclass_type_behavior(
349349
subclasses_disabled_selectors[disable_item.__name__] = disable_item
350350
else:
351351
raise ValueError(
352-
f"Expected 'subclasses_disabled' list items to be types or callables, but got {disable_item!r}"
352+
f"Expected 'subclasses_disabled' list items to be types or functions, but got {disable_item!r}"
353353
)
354354

355355

jsonargparse_tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ def example_parser() -> ArgumentParser:
146146
return parser
147147

148148

149+
@pytest.fixture
150+
def subclass_behavior(monkeypatch) -> Iterator[None]:
151+
monkeypatch.setattr("jsonargparse._common.subclasses_enabled_types", set())
152+
monkeypatch.setattr("jsonargparse._common.subclasses_disabled_types", set())
153+
with patch.dict("jsonargparse._common.subclasses_disabled_selectors"):
154+
yield
155+
156+
149157
@pytest.fixture
150158
def tmp_cwd(tmpdir) -> Iterator[Path]:
151159
with tmpdir.as_cwd():

jsonargparse_tests/test_dataclasses.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Namespace,
1515
set_parsing_settings,
1616
)
17+
from jsonargparse._common import subclasses_disabled_selectors
1718
from jsonargparse._namespace import NSKeyError
1819
from jsonargparse._optionals import (
1920
docstring_parser_support,
@@ -31,6 +32,13 @@
3132

3233
annotated = typing_extensions_import("Annotated")
3334

35+
36+
@pytest.fixture
37+
def enable_subclasses(subclass_behavior):
38+
set_parsing_settings(subclasses_enabled=["is_pure_dataclass"])
39+
yield
40+
41+
3442
BetweenThreeAndNine = restricted_number_type("BetweenThreeAndNine", float, [(">=", 3), ("<=", 9)])
3543
ListPositiveInt = List[PositiveInt]
3644

@@ -768,13 +776,6 @@ def test_add_subclass_dataclass_subclasses_disabled(parser):
768776
parser.add_subclass_arguments(DataMain, "data")
769777

770778

771-
@pytest.fixture
772-
def enable_subclasses():
773-
with patch.dict("jsonargparse._common.subclasses_disabled_selectors"):
774-
set_parsing_settings(subclasses_enabled=["is_pure_dataclass"])
775-
yield
776-
777-
778779
@pytest.mark.parametrize("default", [None, DataMain()])
779780
def test_add_subclass_dataclass_subclasses_enabled(parser, default, enable_subclasses):
780781
parser.add_subclass_arguments(DataMain, "data", default=default)
@@ -849,6 +850,46 @@ def test_add_argument_dataclass_subclasses_enabled(parser, subtests, enable_subc
849850
assert dataclasses.asdict(init.data) == {"p1": 2, "p2": "-"}
850851

851852

853+
def test_add_argument_dataclass_single_type_subclasses_enabled(parser, subclass_behavior):
854+
set_parsing_settings(subclasses_enabled=[DataMain])
855+
assert "is_pure_dataclass" in subclasses_disabled_selectors
856+
857+
parser.add_argument("--data", type=DataMain, default=DataMain(p1=2))
858+
859+
config = {"class_path": f"{__name__}.DataSub", "init_args": {"p2": "y"}}
860+
cfg = parser.parse_args([f"--data={json.dumps(config)}"])
861+
init = parser.instantiate_classes(cfg)
862+
assert isinstance(init.data, DataSub)
863+
assert dataclasses.asdict(init.data) == {"p1": 2, "p2": "y"}
864+
dump = json_or_yaml_load(parser.dump(cfg))["data"]
865+
assert dump == {"class_path": f"{__name__}.DataSub", "init_args": {"p1": 2, "p2": "y"}}
866+
867+
868+
def test_add_argument_dataclass_single_type_subclasses_disabled(parser, enable_subclasses):
869+
set_parsing_settings(subclasses_disabled=[DataMain])
870+
assert "is_pure_dataclass" not in subclasses_disabled_selectors
871+
872+
parser.add_argument("--data", type=DataMain, default=DataMain(p1=2))
873+
874+
config = {"class_path": f"{__name__}.DataSub", "init_args": {"p2": "y"}}
875+
with pytest.raises(ArgumentError, match="Group 'data' does not accept option 'init_args.p2'"):
876+
parser.parse_args([f"--data={json.dumps(config)}"])
877+
878+
879+
def test_add_argument_dataclass_subclasses_disabled_function(parser, enable_subclasses):
880+
def is_data_main(obj):
881+
return obj is DataMain
882+
883+
set_parsing_settings(subclasses_disabled=[is_data_main])
884+
assert "is_pure_dataclass" not in subclasses_disabled_selectors
885+
886+
parser.add_argument("--data", type=DataMain, default=DataMain(p1=2))
887+
888+
config = {"class_path": f"{__name__}.DataSub", "init_args": {"p2": "y"}}
889+
with pytest.raises(ArgumentError, match="Group 'data' does not accept option 'init_args.p2'"):
890+
parser.parse_args([f"--data={json.dumps(config)}"])
891+
892+
852893
class ParentData:
853894
def __init__(self, data: DataMain = DataMain(p1=2)):
854895
self.data = data

jsonargparse_tests/test_parsing_settings.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,25 @@ def test_set_omegaconf_absolute_to_relative_paths_failure():
208208
# enable/disable-subclasses
209209

210210

211-
def test_default_subclass_disable_functions():
211+
def test_default_subclass_disable_functions(subclass_behavior):
212212
from jsonargparse._common import subclasses_disabled_selectors
213213

214-
for name in [
215-
"is_pure_dataclass",
216-
"is_pydantic_model",
217-
"is_attrs_class",
218-
"is_final_class",
219-
]:
220-
assert name in subclasses_disabled_selectors
214+
set_parsing_settings(
215+
subclasses_enabled=["is_pure_dataclass", "is_pydantic_model", "is_attrs_class", "is_final_class"]
216+
)
217+
assert not subclasses_disabled_selectors
218+
219+
220+
def test_unknown_subclass_disable_function():
221+
with pytest.raises(ValueError, match="no function 'unknown_selector'"):
222+
set_parsing_settings(subclasses_enabled=["unknown_selector"])
223+
224+
225+
def test_invalid_item_type_subclass_enable():
226+
with pytest.raises(ValueError, match="Expected 'subclasses_enabled' list items to be types or strings"):
227+
set_parsing_settings(subclasses_enabled=[123])
228+
229+
230+
def test_invalid_item_type_subclass_disable():
231+
with pytest.raises(ValueError, match="Expected 'subclasses_disabled' list items to be types or functions"):
232+
set_parsing_settings(subclasses_disabled=[123])

jsonargparse_tests/test_pydantic.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import json
55
from copy import deepcopy
66
from typing import Dict, List, Literal, Optional, Union
7-
from unittest.mock import patch
87

98
import pytest
109

@@ -40,10 +39,9 @@ def missing_pydantic():
4039

4140

4241
@pytest.fixture
43-
def enable_subclasses():
44-
with patch.dict("jsonargparse._common.subclasses_disabled_selectors"):
45-
set_parsing_settings(subclasses_enabled=["is_pydantic_model"])
46-
yield
42+
def enable_subclasses(subclass_behavior):
43+
set_parsing_settings(subclasses_enabled=["is_pydantic_model"])
44+
yield
4745

4846

4947
@skip_if_pydantic_v1_on_v2

0 commit comments

Comments
 (0)