From 175d169803db339fe5203de9b168e5ae63b4c724 Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:01:33 +0200 Subject: [PATCH] pyyaml is no longer a required dependency --- .github/workflows/tests.yaml | 6 ++--- CHANGELOG.rst | 8 +++++++ CONTRIBUTING.rst | 8 +++---- DOCUMENTATION.rst | 11 +++++---- README.rst | 16 ++++++------- jsonargparse/_core.py | 9 +++++--- jsonargparse/_loaders_dumpers.py | 16 ++++++------- jsonargparse/_optionals.py | 6 +++++ jsonargparse_tests/test_core.py | 19 ++++++++------- jsonargparse_tests/test_jsonnet.py | 9 ++------ jsonargparse_tests/test_loaders_dumpers.py | 27 +++++++++++++++++++++- jsonargparse_tests/test_optionals.py | 18 +++++++++++++++ jsonargparse_tests/test_typehints.py | 17 +++++++------- jsonargparse_tests/test_typing.py | 4 ++-- pyproject.toml | 22 +++++------------- sphinx/migrate_v5.rst | 14 +++++++---- 16 files changed, 128 insertions(+), 82 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a9585e91..072e64df 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -42,10 +42,9 @@ jobs: enable-cache: true cache-suffix: py${{ matrix.python }} cache-dependency-glob: pyproject.toml - - name: Test without optional dependencies and without pyyaml + - name: Test without optional dependencies run: | uv pip install .[coverage] - uv pip uninstall pyyaml types-PyYAML pytest --cov --cov-report=term --cov-report=xml --junit-xml=junit.xml mv coverage.xml coverage_py${{ matrix.python }}_bare.xml mv junit.xml junit_py${{ matrix.python }}_bare.xml @@ -218,11 +217,10 @@ jobs: with: name: package path: dist - - name: Test without optional dependencies and without pyyaml + - name: Test without optional dependencies run: | cd dist uv pip install $(ls jsonargparse-*.whl)[test-no-urls] $(ls jsonargparse_tests-*.whl) - uv pip uninstall pyyaml python -m jsonargparse_tests - name: Test with all optional dependencies run: | diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5a3559b1..6cdfb008 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -38,6 +38,14 @@ Changed - Config objects always include metadata, i.e. ``clone(with_meta=False)`` is now the only way to strip it (`#969 `__). +- ``pyyaml`` is no longer a required dependency, install the ``yaml`` extra to + have it. Without it the default ``parser_mode`` and dump format is ``json``, + and explicitly using ``yaml`` raises an informative ``ImportError`` (`#970 + `__). +- The ``json`` dump format is now indented, so that the print config argument + gives a more readable output. Use the new ``json_compact`` format for the + previous single line output (`#970 + `__). Removed ^^^^^^^ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index df3ebb3d..0300054b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -31,10 +31,10 @@ among them: Development environment ----------------------- -All requirements of the project are defined in ``pyproject.toml``. The basic -runtime requirements are in ``dependencies``. Requirements for optional -features, as well as for testing, development and documentation building -(``test``, ``dev`` and ``doc``), are in ``[project.optional-dependencies]``. +All requirements of the project are defined in ``pyproject.toml``. There are no +required runtime dependencies. Requirements for optional features, as well as +for testing, development and documentation building (``test``, ``dev`` and +``doc``), are in ``[project.optional-dependencies]``. The recommended way to work with the source code is to clone the repository, create a virtual environment, activate it, and install the development diff --git a/DOCUMENTATION.rst b/DOCUMENTATION.rst index 0b5e2874..052ddb9b 100644 --- a/DOCUMENTATION.rst +++ b/DOCUMENTATION.rst @@ -1331,7 +1331,8 @@ Configuration files jsonargparse can parse configuration files (config files). The dot notation hierarchy of the arguments (see :ref:`nested-namespaces`) defines the structure -expected in these files. The default format is YAML. To change it, use the +expected in these files. The default format is YAML, or JSON when the ``yaml`` +extra is not installed, see :ref:`installation`. To change it, use the ``parser_mode`` parameter of the parser, e.g. ``ArgumentParser(parser_mode="toml")``. @@ -1419,8 +1420,8 @@ comma, e.g. ``--print_config=comments,skip_default``: From Python, a config object is serialized with the :meth:`dump <.ArgumentParser.dump>` and :meth:`save <.ArgumentParser.save>` methods. The -supported formats are ``yaml``, ``toml``, ``json``/``json_compact``, -``json_indented`` and ``parser_mode``, the default, which uses the format of the +supported formats are ``yaml``, ``toml``, ``json``/``json_indented``, +``json_compact`` and ``parser_mode``, the default, which uses the format of the parser. More formats are added with :func:`.set_dumper`, for example to dump with PyYAML's ``default_flow_style``: @@ -1441,8 +1442,8 @@ with PyYAML's ``default_flow_style``: Custom loaders -------------- -The ``yaml`` parser mode (see :py:meth:`.ArgumentParser.__init__`) loads with a -subclass of `yaml.SafeLoader +The ``yaml`` parser mode (see :py:meth:`.ArgumentParser.__init__`) requires the +``yaml`` extra and loads with a subclass of `yaml.SafeLoader `__ that has three differences: diff --git a/README.rst b/README.rst index 651f2461..4c647095 100644 --- a/README.rst +++ b/README.rst @@ -185,14 +185,14 @@ You can install using `pip `__ as: pip install jsonargparse -By default, the only dependency installed with ``jsonargparse`` is `PyYAML -`__. However, several optional features can be -enabled by specifying one or more of the following extras (optional -dependencies): ``signatures``, ``jsonschema``, ``jsonnet``, ``urls``, -``fsspec``, ``toml``, ``ruamel``, ``omegaconf``, ``shtab``, and ``argcomplete``. -Additionally, the ``all`` extras can be used to enable all optional features -(excluding tab completion ones). To install ``jsonargparse`` with extras, use -the following syntax: +``jsonargparse`` has no required dependencies. Optional features are enabled by +specifying one or more of the following extras (optional dependencies): +``signatures``, ``yaml``, ``jsonschema``, ``jsonnet``, ``urls``, ``fsspec``, +``toml``, ``ruamel``, ``omegaconf``, ``shtab``, and ``argcomplete``. The +``yaml`` extra installs `PyYAML `__, without +which config files are parsed as JSON. Additionally, the ``all`` extras can be +used to enable all optional features (excluding tab completion ones). To install +``jsonargparse`` with extras, use the following syntax: .. code-block:: bash diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py index 1bb1c621..a6c7f07b 100644 --- a/jsonargparse/_core.py +++ b/jsonargparse/_core.py @@ -64,6 +64,7 @@ fsspec_support, import_fsspec, import_jsonnet, + import_pyyaml, omegaconf_apply, pyyaml_available, ) @@ -791,8 +792,8 @@ def dump( Args: namespace: The configuration object to dump. - format: The output format: ``yaml``, ``json``, ``json_indented``, ``toml``, ``parser_mode`` or ones added - via :func:`.set_dumper`. + format: The output format: ``yaml``, ``json``, ``json_compact``, ``toml``, ``parser_mode`` or ones + added via :func:`.set_dumper`. skip_unset: Whether to exclude entries whose value is the configured None/Unset value. skip_default: Whether to exclude entries whose value is the same as the default. skip_validation: Whether to skip parser checking. @@ -904,7 +905,7 @@ def save( Args: namespace: The configuration object to save. path: Path to the location where to save config. - format: The output format: ``yaml``, ``json``, ``json_indented``, ``parser_mode`` or ones added via + format: The output format: ``yaml``, ``json``, ``json_compact``, ``parser_mode`` or ones added via :func:`.set_dumper`. skip_unset: Whether to exclude entries whose value is the configured None/Unset value. skip_validation: Whether to skip parser checking. @@ -1533,6 +1534,8 @@ def parser_mode(self, parser_mode: str): raise ValueError(f"The only accepted values for parser_mode are {accepted}.") if parser_mode == "jsonnet": import_jsonnet("parser_mode=jsonnet") + elif parser_mode == "yaml": + import_pyyaml("parser_mode=yaml") self._parser_mode = parser_mode if self._subcommands_action: for subparser in self._subcommands_action._name_parser_map.values(): diff --git a/jsonargparse/_loaders_dumpers.py b/jsonargparse/_loaders_dumpers.py index e2f837ac..c26625c9 100644 --- a/jsonargparse/_loaders_dumpers.py +++ b/jsonargparse/_loaders_dumpers.py @@ -10,6 +10,7 @@ from ._common import Unset, get_parsing_setting, load_value_mode, parent_parser from ._optionals import ( import_jsonnet, + import_pyyaml, import_toml_dumps, import_toml_loads, omegaconf_support, @@ -54,7 +55,7 @@ def get_yaml_default_loader(): if yaml_default_loader: return yaml_default_loader - import yaml + yaml = import_pyyaml("get_yaml_default_loader") class DefaultLoader(getattr(yaml, "CSafeLoader", yaml.SafeLoader)): pass @@ -92,8 +93,7 @@ def remove_implicit_resolver(cls, tag_to_remove): def yaml_load(stream): - import yaml - + yaml = import_pyyaml("yaml_load") value = yaml.load(stream, Loader=get_yaml_default_loader()) if isinstance(value, dict) and value and all(v is None for v in value.values()): if len(value) == 1 and stream.strip() == next(iter(value)) + ":": @@ -159,7 +159,7 @@ def get_loader_exceptions(mode: str | None = None) -> tuple[type[Exception], ... mode = get_load_value_mode() if mode not in loader_exceptions: if mode == "yaml": - loader_exceptions[mode] = (__import__("yaml").YAMLError,) + loader_exceptions[mode] = (import_pyyaml("get_loader_exceptions").YAMLError,) elif mode == "json": loader_exceptions[mode] = (__import__("json").JSONDecodeError,) elif mode == "toml": @@ -247,8 +247,7 @@ def replace_unset(data): def yaml_dump(data): - import yaml - + yaml = import_pyyaml("yaml_dump") return yaml.safe_dump(data, **dump_yaml_kwargs) @@ -278,7 +277,7 @@ def toml_dump(data): dumpers: dict[str, Callable] = { "yaml": yaml_dump, - "json": json_compact_dump, + "json": json_indented_dump, "json_compact": json_compact_dump, "json_indented": json_indented_dump, "toml": toml_dump, @@ -302,7 +301,8 @@ def check_valid_dump_format(dump_format: str): def dump_using_format(parser: ArgumentParser, data: dict, dump_format: str, with_comments: bool = False) -> str: if dump_format == "parser_mode": - dump_format = parser.parser_mode if parser.parser_mode in dumpers else "yaml" + default_format = "yaml" if pyyaml_available else "json" + dump_format = parser.parser_mode if parser.parser_mode in dumpers else default_format if with_comments: if f"{dump_format}_comments" not in dumpers: if dump_format == "yaml": diff --git a/jsonargparse/_optionals.py b/jsonargparse/_optionals.py index d8e6bce0..2694c0e4 100644 --- a/jsonargparse/_optionals.py +++ b/jsonargparse/_optionals.py @@ -101,6 +101,12 @@ def missing_package_raise(package, importer): raise ImportError(f"{package} package is required by {importer} :: {ex}") from ex +def import_pyyaml(importer): + with missing_package_raise("PyYAML", importer): + import yaml + return yaml + + def import_toml_loads(importer): if find_spec("tomllib"): import tomllib diff --git a/jsonargparse_tests/test_core.py b/jsonargparse_tests/test_core.py index 54c02194..a5649278 100644 --- a/jsonargparse_tests/test_core.py +++ b/jsonargparse_tests/test_core.py @@ -565,17 +565,17 @@ def test_dump_formats(dump_parser): if pyyaml_available: assert dump_parser.dump(cfg) == "op1: 123\nop2: abc\n" assert dump_parser.dump(cfg, format="yaml") == dump_parser.dump(cfg) - assert dump_parser.dump(cfg, format="json") == '{"op1":123,"op2":"abc"}' - assert dump_parser.dump(cfg, format="json_indented") == '{\n "op1": 123,\n "op2": "abc"\n}\n' + assert dump_parser.dump(cfg, format="json") == '{\n "op1": 123,\n "op2": "abc"\n}\n' + assert dump_parser.dump(cfg, format="json_indented") == dump_parser.dump(cfg, format="json") + assert dump_parser.dump(cfg, format="json_compact") == '{"op1":123,"op2":"abc"}' pytest.raises(ValueError, lambda: dump_parser.dump(cfg, format="invalid")) def test_dump_skip_default_simple(dump_parser): dump = dump_parser.dump(dump_parser.get_defaults(), skip_default=True) - expected = "{}\n" if pyyaml_available else "{}" - assert dump == expected + assert dump == "{}\n" dump = dump_parser.dump(Namespace(op1=123, op2="xyz"), skip_default=True) - expected = "op2: xyz\n" if pyyaml_available else '{"op2":"xyz"}' + expected = "op2: xyz\n" if pyyaml_available else '{\n "op2": "xyz"\n}\n' assert dump == expected @@ -585,13 +585,12 @@ def test_dump_skip_default_nested(parser): parser.add_argument("--g2.op1", type=int, default=987) parser.add_argument("--g2.op2", type=str, default="xyz") dump = parser.dump(parser.get_defaults(), skip_default=True) - expected = "{}\n" if pyyaml_available else "{}" - assert dump == expected + assert dump == "{}\n" dump = parser.dump(parser.parse_args(["--g1.op1=0"]), skip_default=True) - expected = "g1:\n op1: 0\n" if pyyaml_available else '{"g1":{"op1":0}}' + expected = "g1:\n op1: 0\n" if pyyaml_available else '{\n "g1": {\n "op1": 0\n }\n}\n' assert dump == expected dump = parser.dump(parser.parse_args(["--g2.op2=pqr"]), skip_default=True) - expected = "g2:\n op2: pqr\n" if pyyaml_available else '{"g2":{"op2":"pqr"}}' + expected = "g2:\n op2: pqr\n" if pyyaml_available else '{\n "g2": {\n "op2": "pqr"\n }\n}\n' assert dump == expected @@ -841,7 +840,7 @@ def test_save_path_content(parser, tmp_cwd): parser.save_path_content.add("the.path") parser.save(cfg, out_yaml) - expected = "the:\n path: file.txt\n" if pyyaml_available else '{"the":{"path":"file.txt"}}' + expected = "the:\n path: file.txt\n" if pyyaml_available else '{\n "the": {\n "path": "file.txt"\n }\n}\n' assert out_yaml.read_text() == expected assert out_file.read_text() == "file content" diff --git a/jsonargparse_tests/test_jsonnet.py b/jsonargparse_tests/test_jsonnet.py index 9a1c180b..d0ceff63 100644 --- a/jsonargparse_tests/test_jsonnet.py +++ b/jsonargparse_tests/test_jsonnet.py @@ -12,7 +12,7 @@ ArgumentError, ArgumentParser, ) -from jsonargparse._optionals import jsonnet_support, pyyaml_available +from jsonargparse._optionals import jsonnet_support from jsonargparse_tests.conftest import ( get_parser_help, json_or_yaml_load, @@ -177,12 +177,7 @@ def test_action_jsonnet_save_config_metadata(parser, tmp_path): # rewrite the config to make sure that ext_vars is after jsonnet main_cfg = json_or_yaml_load(output_config.read_text()) main_cfg = {k: main_cfg[k] for k in ["jsonnet", "ext_vars"]} - if pyyaml_available: - import yaml - - output_config.write_text(yaml.safe_dump(main_cfg, sort_keys=False)) - else: - output_config.write_text(json.dumps(main_cfg)) + output_config.write_text(json.dumps(main_cfg)) # parse using saved config and verify result is the same cfg2 = parser.parse_args([f"--cfg={output_config}"]) diff --git a/jsonargparse_tests/test_loaders_dumpers.py b/jsonargparse_tests/test_loaders_dumpers.py index e3042135..f0045d69 100644 --- a/jsonargparse_tests/test_loaders_dumpers.py +++ b/jsonargparse_tests/test_loaders_dumpers.py @@ -53,6 +53,31 @@ def test_invalid_parser_mode(): pytest.raises(ValueError, lambda: ArgumentParser(parser_mode="invalid")) +@skip_if_no_pyyaml +def test_default_parser_mode_yaml(): + assert ArgumentParser().parser_mode == "yaml" + + +@pytest.mark.skipif(pyyaml_available, reason="PyYAML package should not be installed") +def test_without_pyyaml_default_parser_mode_json(): + assert ArgumentParser().parser_mode == "json" + + +@pytest.mark.skipif(pyyaml_available, reason="PyYAML package should not be installed") +def test_without_pyyaml_parser_mode_yaml_error(): + with pytest.raises(ImportError) as ctx: + ArgumentParser(parser_mode="yaml") + ctx.match("PyYAML package is required by parser_mode=yaml") + + +@pytest.mark.skipif(pyyaml_available, reason="PyYAML package should not be installed") +def test_without_pyyaml_dump_yaml_error(parser): + parser.add_argument("--int", type=int, default=1) + with pytest.raises(ImportError) as ctx: + parser.dump(parser.get_defaults(), format="yaml") + ctx.match("PyYAML package is required by yaml_dump") + + def test_get_loader(): from jsonargparse._loaders_dumpers import jsonnet_load @@ -82,7 +107,7 @@ def test_dump_header_json(parser): parser.add_argument("--int", type=int, default=1) parser.dump_header = ["line 1", "line 2"] dump = parser.dump(parser.get_defaults(), format="json") - assert dump == '{"int":1}' + assert dump == '{\n "int": 1\n}\n' def test_dump_header_invalid(parser): diff --git a/jsonargparse_tests/test_optionals.py b/jsonargparse_tests/test_optionals.py index af5071eb..53f15c63 100644 --- a/jsonargparse_tests/test_optionals.py +++ b/jsonargparse_tests/test_optionals.py @@ -15,10 +15,12 @@ import_fsspec, import_jsonnet, import_jsonschema, + import_pyyaml, import_requests, import_ruamel, jsonnet_support, jsonschema_support, + pyyaml_available, ruamel_support, url_support, ) @@ -28,9 +30,25 @@ get_parser_help, skip_if_docstring_parser_unavailable, skip_if_fsspec_unavailable, + skip_if_no_pyyaml, skip_if_requests_unavailable, ) +# pyyaml support + + +@skip_if_no_pyyaml +def test_pyyaml_support_true(): + import_pyyaml("test_pyyaml_support_true") + + +@pytest.mark.skipif(pyyaml_available, reason="PyYAML package should not be installed") +def test_pyyaml_support_false(): + with pytest.raises(ImportError) as ctx: + import_pyyaml("test_pyyaml_support_false") + ctx.match("test_pyyaml_support_false") + + # jsonschema support diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py index b12e4ecc..ff35ef9d 100644 --- a/jsonargparse_tests/test_typehints.py +++ b/jsonargparse_tests/test_typehints.py @@ -195,7 +195,6 @@ def test_float_scientific_notation(parser): @parser_modes def test_float_implicit_leading_zero(parser): - parser.parser_mode = "yaml" parser.add_argument("--num", type=float) assert 0.5 == parser.parse_args(["--num=.5"]).num assert -0.5 == parser.parse_args(["--num=-.5"]).num @@ -745,7 +744,7 @@ def test_frozenset(parser): parser.add_argument("--frozen", type=FrozenSet[int]) cfg = parser.parse_args(["--frozen=[1, 2]"]) assert frozenset([1, 2]) == cfg.frozen - assert parser.dump(cfg, format="json") == '{"frozen":[1,2]}' + assert parser.dump(cfg, format="json_compact") == '{"frozen":[1,2]}' with pytest.raises(ArgumentError) as ctx: parser.parse_args(['--frozen=["a", "b"]']) ctx.match("Expected a ") @@ -756,7 +755,7 @@ def test_abstract_set(parser, set_type): parser.add_argument("--set", type=set_type[int]) cfg = parser.parse_args(["--set=[1, 2]"]) assert {1, 2} == cfg.set - assert parser.dump(cfg, format="json") == '{"set":[1,2]}' + assert parser.dump(cfg, format="json_compact") == '{"set":[1,2]}' with pytest.raises(ArgumentError) as ctx: parser.parse_args(['--set=["a", "b"]']) ctx.match("Expected a ") @@ -851,7 +850,7 @@ def test_deque(parser): cfg = parser.parse_args(["--deque=[1, 2]"]) assert isinstance(cfg.deque, deque) assert deque([1, 2]) == cfg.deque - assert parser.dump(cfg, format="json") == '{"deque":[1,2]}' + assert parser.dump(cfg, format="json_compact") == '{"deque":[1,2]}' def test_list_dump(parser): @@ -1558,7 +1557,7 @@ def test_unsubscripted_sequence_alias(parser, alias, expected): parser.add_argument("--x", type=alias) cfg = parser.parse_args(["--x=[1, 2]"]) assert cfg.x == expected - assert parser.dump(cfg, format="json") == '{"x":[1,2]}' + assert parser.dump(cfg, format="json_compact") == '{"x":[1,2]}' @pytest.mark.parametrize("alias", [Dict, Mapping, MutableMapping], ids=str) @@ -1566,7 +1565,7 @@ def test_unsubscripted_mapping_alias(parser, alias): parser.add_argument("--x", type=alias) cfg = parser.parse_args(['--x={"a": 1}']) assert cfg.x == {"a": 1} - assert parser.dump(cfg, format="json") == '{"x":{"a":1}}' + assert parser.dump(cfg, format="json_compact") == '{"x":{"a":1}}' @pytest.mark.parametrize("alias", [List, Iterable, Deque], ids=str) @@ -2660,7 +2659,7 @@ def test_mapping_proxy_type(parser): cfg = parser.parse_args(['--mapping={"x":1}']) assert isinstance(cfg.mapping, MappingProxyType) assert cfg.mapping == {"x": 1} - assert parser.dump(cfg, format="json") == '{"mapping":{"x":1}}' + assert parser.dump(cfg, format="json_compact") == '{"mapping":{"x":1}}' def test_mapping_default_mapping_proxy_type(parser): @@ -2669,7 +2668,7 @@ def test_mapping_default_mapping_proxy_type(parser): cfg = parser.parse_args([]) assert isinstance(cfg.mapping, Mapping) assert mapping_proxy == cfg.mapping - assert parser.dump(cfg, format="json") == '{"mapping":{"x":1}}' + assert parser.dump(cfg, format="json_compact") == '{"mapping":{"x":1}}' def test_ordered_dict(parser): @@ -2680,7 +2679,7 @@ def test_ordered_dict(parser): with pytest.raises(ArgumentError) as ctx: parser.parse_args(['--odict={"x":"-"}']) ctx.match("Expected a ") - assert parser.dump(cfg, format="json") == '{"odict":{"a":1,"b":2}}' + assert parser.dump(cfg, format="json_compact") == '{"odict":{"a":1,"b":2}}' if pyyaml_available: assert parser.dump(cfg, format="yaml") == "odict:\n a: 1\n b: 2\n" diff --git a/jsonargparse_tests/test_typing.py b/jsonargparse_tests/test_typing.py index 4b7b6d0b..f20dc15b 100644 --- a/jsonargparse_tests/test_typing.py +++ b/jsonargparse_tests/test_typing.py @@ -378,8 +378,8 @@ def __bool__(self): parser.add_argument("--elems", type=Elems) cfg = parser.parse_args(["--elems=[1, 2, 3]"]) assert isinstance(cfg.elems, Elems) - assert [1, 2, 3] == cfg.elems.elems - assert '{"elems":[1,2,3]}' == parser.dump(cfg, format="json") + assert cfg.elems.elems == [1, 2, 3] + assert parser.dump(cfg, format="json_compact") == '{"elems":[1,2,3]}' def test_register_type_datetime(parser): diff --git a/pyproject.toml b/pyproject.toml index 51242735..cb38d3b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,17 +31,13 @@ classifiers = [ "Operating System :: Microsoft :: Windows", ] -dependencies = [ - "PyYAML>=3.13", -] - [project.optional-dependencies] all = [ "jsonargparse[signatures]", "jsonargparse[yaml]", - "jsonargparse[jsonschema]", - "jsonargparse[jsonnet]", "jsonargparse[toml]", + "jsonargparse[jsonnet]", + "jsonargparse[jsonschema]", "jsonargparse[urls]", "jsonargparse[fsspec]", "jsonargparse[ruamel]", @@ -91,8 +87,8 @@ test = [ "jsonargparse[test-no-urls]", "jsonargparse[shtab]", "jsonargparse[argcomplete]", - "types-PyYAML>=6.0.11", - "types-requests>=2.28.9,<2.34", + "types-PyYAML>=6.0.11", # required by test_stubs_resolver + "types-requests>=2.28.9,<2.34", # required by test_stubs_resolver "requests<2.34", "responses>=0.12.0", "pydantic>=2.3.0", @@ -190,9 +186,9 @@ Villegas = "Villegas" [tool.tox] legacy_tox_ini = """ [tox] -envlist = py{310,311,312,313,314}-{all-extras,no-extras,argparse},omegaconf,pydantic-v1,without-pyyaml,without-future-annotations +envlist = py{310,311,312,313,314}-{all-extras,no-extras,argparse},omegaconf,pydantic-v1,without-future-annotations labels = - coverage = py{310,311,312,313,314}-{all-extras,no-extras},omegaconf,pydantic-v1,without-pyyaml,without-future-annotations + coverage = py{310,311,312,313,314}-{all-extras,no-extras},omegaconf,pydantic-v1,without-future-annotations skip_missing_interpreters = true [testenv] @@ -258,12 +254,6 @@ commands = uv pip install --python {envpython} "pydantic>=2" python -m pytest {posargs} {envtmpdir}/test_pydantic.py -[testenv:without-pyyaml] -extras = test,coverage,all -commands = - uv pip uninstall --python {envpython} argcomplete omegaconf pyyaml responses ruamel.yaml ruamel.yaml.clib types-PyYAML - python -m pytest {posargs} - [testenv:without-future-annotations] extras = test,coverage,all # editable, so that changes in the tests source are picked up without recreating the env diff --git a/sphinx/migrate_v5.rst b/sphinx/migrate_v5.rst index fb4e958b..2a2b61bf 100644 --- a/sphinx/migrate_v5.rst +++ b/sphinx/migrate_v5.rst @@ -15,7 +15,7 @@ approach is: JSONARGPARSE_DEPRECATION_WARNINGS=all python your_script.py 3. **Fix all deprecation warnings** as described in the sections below. -4. **Review the** `Breaking changes summary`_, since one change does not emit a +4. **Review the** `Breaking changes summary`_, since some changes do not emit a deprecation warning. 5. **Upgrade to v5** (``pip install "jsonargparse>=5"``). @@ -32,10 +32,14 @@ Breaking changes summary In addition to the deprecation removals below, note these other breaking changes: -- **``pyyaml`` is no longer a required dependency.** This is the only change - that gives no deprecation warning. If your code imports ``yaml``, or you want - yaml configs, install with the ``yaml`` extra (``pip install - "jsonargparse[yaml]"``). +- **``pyyaml`` is no longer a required dependency.** Gives no deprecation + warning. Without it the default ``parser_mode`` and dump format is ``json``. + If your code imports ``yaml``, or you want yaml configs, install with the + ``yaml`` extra (``pip install "jsonargparse[yaml]"``). +- **``json`` dumps are now indented.** Gives no deprecation warning. + ``dump(format="json")``, ``save`` and the print config argument produce + indented JSON instead of a single line. Use ``format="json_compact"`` for the + previous output. - **``--print_config`` renamed.** The print-config argument in v5 defaults to ``--print_`` instead of always being ``--print_config``, so it only stays ``--print_config`` when the config argument is named ``config``.