diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 27c02935..2ea59b2e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,6 +28,8 @@ Fixed `__). - Targets of links applied on parse not being instantiated (`#834 `__). +- Validation of defaults getting stuck for path with ``-`` (stdin) default + (`#837 `__). v4.45.0 (2025-12-26) diff --git a/jsonargparse/_common.py b/jsonargparse/_common.py index 8ae3c90d..09cdc30d 100644 --- a/jsonargparse/_common.py +++ b/jsonargparse/_common.py @@ -59,6 +59,7 @@ def __call__(self, class_type: type[ClassType], *args, **kwargs) -> ClassType: defaults_cache: ContextVar[Optional[Namespace]] = ContextVar("defaults_cache", default=None) lenient_check: ContextVar[Union[bool, str]] = ContextVar("lenient_check", default=False) parsing_defaults: ContextVar[bool] = ContextVar("parsing_defaults", default=False) +validating_defaults: ContextVar[bool] = ContextVar("validating_defaults", default=False) load_value_mode: ContextVar[Optional[str]] = ContextVar("load_value_mode", default=None) class_instantiators: ContextVar[Optional[InstantiatorsDictType]] = ContextVar("class_instantiators", default=None) nested_links: ContextVar[list[dict]] = ContextVar("nested_links", default=[]) @@ -72,6 +73,7 @@ def __call__(self, class_type: type[ClassType], *args, **kwargs) -> ClassType: "defaults_cache": defaults_cache, "lenient_check": lenient_check, "parsing_defaults": parsing_defaults, + "validating_defaults": validating_defaults, "load_value_mode": load_value_mode, "class_instantiators": class_instantiators, "nested_links": nested_links, @@ -207,7 +209,7 @@ def validate_default(container: ActionsContainer, action: argparse.Action): if isinstance(container, ArgumentGroup): container = container.parser # type: ignore[assignment] - with parser_context(parent_parser=container): + with parser_context(parent_parser=container, validating_defaults=True): default = action.default action.default = None action.default = action._check_type_(default) # type: ignore[attr-defined] diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py index 94327451..be3d4cc7 100644 --- a/jsonargparse/_core.py +++ b/jsonargparse/_core.py @@ -1037,7 +1037,8 @@ def get_defaults(self, skip_validation: bool = False, **kwargs) -> Namespace: cfg["__default_config__"] = default_config_file self._logger.debug("Parsed default configuration from path: %s", default_config_file) - ActionTypeHint.add_sub_defaults(self, cfg) + with parser_context(validating_defaults=True): + ActionTypeHint.add_sub_defaults(self, cfg) return cfg diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py index 70e6d435..06b053e8 100644 --- a/jsonargparse/_typehints.py +++ b/jsonargparse/_typehints.py @@ -59,6 +59,7 @@ nested_links, parent_parser, parser_context, + validating_defaults, ) from ._loaders_dumpers import ( get_loader_exceptions, @@ -897,6 +898,8 @@ def adapt_typehints( prev_val = prev_val + [None] * (len(val) - len(prev_val) if val_is_list else 1) list_path = None if enable_path and type(val) is str: + if validating_defaults.get(): + return val with suppress(TypeError): from ._optionals import _get_config_read_mode diff --git a/jsonargparse_tests/conftest.py b/jsonargparse_tests/conftest.py index faa9d830..e9f469ca 100644 --- a/jsonargparse_tests/conftest.py +++ b/jsonargparse_tests/conftest.py @@ -193,6 +193,15 @@ def logger() -> logging.Logger: return logger +def patch_parsing_settings(fn): + @wraps(fn) + def _patch_parsing_settings(*args, **kwargs): + with patch.dict("jsonargparse._common.parsing_settings"): + return fn(*args, **kwargs) + + return _patch_parsing_settings + + @contextmanager def capture_logs(logger: logging.Logger) -> Iterator[StringIO]: with ExitStack() as stack: diff --git a/jsonargparse_tests/test_parsing_settings.py b/jsonargparse_tests/test_parsing_settings.py index d4473d2c..a633b10d 100644 --- a/jsonargparse_tests/test_parsing_settings.py +++ b/jsonargparse_tests/test_parsing_settings.py @@ -12,7 +12,7 @@ @pytest.fixture(autouse=True) -def patch_parsing_settings(): +def auto_patch_parsing_settings(): with patch.dict("jsonargparse._common.parsing_settings"): yield diff --git a/jsonargparse_tests/test_paths.py b/jsonargparse_tests/test_paths.py index ccc03a62..cf676cc8 100644 --- a/jsonargparse_tests/test_paths.py +++ b/jsonargparse_tests/test_paths.py @@ -12,7 +12,7 @@ import pytest -from jsonargparse import ArgumentError, Namespace +from jsonargparse import ArgumentError, Namespace, set_parsing_settings from jsonargparse._optionals import fsspec_support, url_support from jsonargparse._paths import _current_path_dir, _parse_url from jsonargparse.typing import Path, Path_drw, Path_fc, Path_fr, path_type @@ -21,6 +21,7 @@ is_posix, json_or_yaml_dump, json_or_yaml_load, + patch_parsing_settings, responses_activate, responses_available, skip_if_fsspec_unavailable, @@ -524,6 +525,16 @@ def test_paths_dump(parser, tmp_cwd): assert json_or_yaml_load(parser.dump(cfg)) == {"paths": ["path1", "path2"]} +def test_path_fr_default_stdin(parser): + parser.add_argument("--path", type=Path_fr, default="-") + + defaults = parser.get_defaults() + assert defaults.path == Path_fr("-") + + defaults = parser.parse_args([]) + assert defaults.path == Path_fr("-") + + # enable_path tests @@ -622,7 +633,10 @@ def test_enable_path_list_path_fr(parser, tmp_cwd, mock_stdin, subtests): ctx.match("Expected a path but no-such-file either not accessible or invalid") -def test_enable_path_list_path_fr_default_stdin(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): + set_parsing_settings(validate_defaults=validate_defaults) (tmp_cwd / "file1").touch() (tmp_cwd / "file2").touch() @@ -633,6 +647,10 @@ def test_enable_path_list_path_fr_default_stdin(parser, tmp_cwd, mock_stdin, sub default="-", ) + with subtests.test("defaults"): + defaults = parser.get_defaults() + assert defaults.list == "-" + with subtests.test("without args"): with mock_stdin("file1\nfile2\n"): cfg = parser.parse_args([])