diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 763ffe3a..7abbd07d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -49,6 +49,8 @@ Changed - ``YAML`` comments feature is now implemented in a separate class to allow better support for custom help formatters without breaking the comments (`#754 `__). +- Replaced unmaintained optional dependency ``ruyaml`` with ``ruamel.yaml`` + (`#768 `__). Deprecated ^^^^^^^^^^ @@ -56,6 +58,9 @@ Deprecated removed in v5.0.0. This logic has been moved to a new private class ``YAMLCommentFormatter``. If deemed necessary, this class might be made public in the future (`#754 `__). +- The ``ruyaml`` optional dependency is deprecated and will be removed in + v5.0.0. Instead use the ``ruamel`` optional dependency (`#768 + `__). v4.40.2 (2025-08-06) diff --git a/DOCUMENTATION.rst b/DOCUMENTATION.rst index 66cc09da..b2114c9b 100644 --- a/DOCUMENTATION.rst +++ b/DOCUMENTATION.rst @@ -1321,9 +1321,9 @@ Serialization Parsers that have an `action="config"` argument also include a ``--print_config`` option. This is useful particularly for command line tools with a large set of options to create an initial config file including all -default values. If the `ruyaml `__ package is -installed, the config can be printed having the help descriptions content as -YAML comments by using ``--print_config=comments``. Another option is +default values. If the `ruamel.yaml `__ +package is installed, the config can be printed having the help descriptions +content as YAML comments by using ``--print_config=comments``. Another option is ``--print_config=skip_null`` which skips entries whose value is ``null``. From within Python it is also possible to serialize a config object by using diff --git a/README.rst b/README.rst index 533230ca..16f4cdfb 100644 --- a/README.rst +++ b/README.rst @@ -183,7 +183,7 @@ 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``, ``ruyaml``, ``omegaconf``, ``shtab``, and ``argcomplete``. +``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: diff --git a/jsonargparse/_actions.py b/jsonargparse/_actions.py index 0ec4c686..4aa50edc 100644 --- a/jsonargparse/_actions.py +++ b/jsonargparse/_actions.py @@ -12,7 +12,7 @@ from ._common import Action, is_subclass, parser_context from ._loaders_dumpers import get_loader_exceptions, load_value from ._namespace import Namespace, NSKeyError, split_key, split_key_root -from ._optionals import _get_config_read_mode, ruyaml_support +from ._optionals import _get_config_read_mode, ruamel_support from ._type_checking import ActionsContainer, ArgumentParser from ._util import ( Path, @@ -254,13 +254,13 @@ def __init__( "flags customizes the output and are one or more keywords separated by comma. The " "supported flags are:%s skip_default, skip_null." ) - % (" comments," if ruyaml_support else ""), + % (" comments," if ruamel_support else ""), ) def __call__(self, parser, namespace, value, option_string=None): kwargs = {"subparser": parser, "key": None, "skip_none": False, "skip_validation": False} valid_flags = {"": None, "skip_default": "skip_default", "skip_null": "skip_none"} - if ruyaml_support: + if ruamel_support: valid_flags["comments"] = "yaml_comments" if value is not None: flags = value[0].split(",") diff --git a/jsonargparse/_deprecated.py b/jsonargparse/_deprecated.py index dd4ccf4f..dc1475e2 100644 --- a/jsonargparse/_deprecated.py +++ b/jsonargparse/_deprecated.py @@ -14,7 +14,7 @@ from ._common import Action, null_logger from ._common import LoggerProperty as InternalLoggerProperty from ._namespace import Namespace -from ._type_checking import ArgumentParser, ruyamlCommentedMap +from ._type_checking import ArgumentParser, ruamelCommentedMap __all__ = [ "ActionEnum", @@ -711,34 +711,34 @@ def add_yaml_comments(self, cfg: str) -> str: return self._yaml_formatter.add_yaml_comments(cfg) @deprecated("The set_yaml_start_comment method is deprecated and will be removed in v5.0.0.") - def set_yaml_start_comment(self, text: str, cfg: ruyamlCommentedMap): - """Sets the start comment to a ruyaml object. + def set_yaml_start_comment(self, text: str, cfg: ruamelCommentedMap): + """Sets the start comment to a ruamel.yaml object. Args: text: The content to use for the comment. - cfg: The ruyaml object. + cfg: The ruamel.yaml object. """ self._yaml_formatter.set_yaml_start_comment(text, cfg) @deprecated("The set_yaml_group_comment method is deprecated and will be removed in v5.0.0.") - def set_yaml_group_comment(self, text: str, cfg: ruyamlCommentedMap, key: str, depth: int): - """Sets the comment for a group to a ruyaml object. + def set_yaml_group_comment(self, text: str, cfg: ruamelCommentedMap, key: str, depth: int): + """Sets the comment for a group to a ruamel.yaml object. Args: text: The content to use for the comment. - cfg: The parent ruyaml object. + cfg: The parent ruamel.yaml object. key: The key of the group. depth: The nested level of the group. """ self._yaml_formatter.set_yaml_group_comment(text, cfg, key, depth) @deprecated("The set_yaml_argument_comment method is deprecated and will be removed in v5.0.0.") - def set_yaml_argument_comment(self, text: str, cfg: ruyamlCommentedMap, key: str, depth: int): - """Sets the comment for an argument to a ruyaml object. + def set_yaml_argument_comment(self, text: str, cfg: ruamelCommentedMap, key: str, depth: int): + """Sets the comment for an argument to a ruamel.yaml object. Args: text: The content to use for the comment. - cfg: The parent ruyaml object. + cfg: The parent ruamel.yaml object. key: The key of the argument. depth: The nested level of the argument. """ diff --git a/jsonargparse/_formatters.py b/jsonargparse/_formatters.py index 07a77a7a..bf117ae9 100644 --- a/jsonargparse/_formatters.py +++ b/jsonargparse/_formatters.py @@ -33,8 +33,8 @@ from ._deprecated import HelpFormatterDeprecations from ._link_arguments import ActionLink from ._namespace import Namespace, NSKeyError -from ._optionals import import_ruyaml -from ._type_checking import ArgumentParser, ruyamlCommentedMap +from ._optionals import import_ruamel +from ._type_checking import ArgumentParser, ruamelCommentedMap from ._typehints import ActionTypeHint, type_to_str __all__ = ["DefaultHelpFormatter"] @@ -63,7 +63,7 @@ def __init__(self, help_formatter: HelpFormatter): def add_yaml_comments(self, cfg: str) -> str: """Adds help text as yaml comments.""" - ruyaml = import_ruyaml("add_yaml_comments") + ruyaml = import_ruamel("add_yaml_comments") yaml = ruyaml.YAML() cfg = yaml.load(cfg) @@ -119,28 +119,28 @@ def set_comments(cfg, prefix="", depth=0): def set_yaml_start_comment( self, text: str, - cfg: ruyamlCommentedMap, + cfg: ruamelCommentedMap, ): - """Sets the start comment to a ruyaml object. + """Sets the start comment to a ruamel.yaml object. Args: text: The content to use for the comment. - cfg: The ruyaml object. + cfg: The ruamel.yaml object. """ cfg.yaml_set_start_comment(text) def set_yaml_group_comment( self, text: str, - cfg: ruyamlCommentedMap, + cfg: ruamelCommentedMap, key: str, depth: int, ): - """Sets the comment for a group to a ruyaml object. + """Sets the comment for a group to a ruamel.yaml object. Args: text: The content to use for the comment. - cfg: The parent ruyaml object. + cfg: The parent ruamel.yaml object. key: The key of the group. depth: The nested level of the group. """ @@ -149,15 +149,15 @@ def set_yaml_group_comment( def set_yaml_argument_comment( self, text: str, - cfg: ruyamlCommentedMap, + cfg: ruamelCommentedMap, key: str, depth: int, ): - """Sets the comment for an argument to a ruyaml object. + """Sets the comment for an argument to a ruamel.yaml object. Args: text: The content to use for the comment. - cfg: The parent ruyaml object. + cfg: The parent ruamel.yaml object. key: The key of the argument. depth: The nested level of the argument. """ diff --git a/jsonargparse/_loaders_dumpers.py b/jsonargparse/_loaders_dumpers.py index 0354bc7c..66932892 100644 --- a/jsonargparse/_loaders_dumpers.py +++ b/jsonargparse/_loaders_dumpers.py @@ -13,7 +13,7 @@ import_toml_loads, omegaconf_support, pyyaml_available, - ruyaml_support, + ruamel_support, ) from ._type_checking import ArgumentParser @@ -263,7 +263,7 @@ def toml_dump(data): "toml": toml_dump, "jsonnet": json_indented_dump, } -if ruyaml_support: +if ruamel_support: dumpers["yaml_comments"] = yaml_comments_dump comment_prefix: Dict[str, str] = { diff --git a/jsonargparse/_optionals.py b/jsonargparse/_optionals.py index 082da482..8b6fb667 100644 --- a/jsonargparse/_optionals.py +++ b/jsonargparse/_optionals.py @@ -22,7 +22,7 @@ url_support = find_spec("requests") is not None docstring_parser_support = find_spec("docstring_parser") is not None fsspec_support = find_spec("fsspec") is not None -ruyaml_support = find_spec("ruyaml") is not None +ruamel_support = bool(find_spec("ruamel") and find_spec("ruamel.yaml")) omegaconf_support = find_spec("omegaconf") is not None reconplogger_support = find_spec("reconplogger") is not None attrs_support = find_spec("attrs") is not None @@ -152,10 +152,10 @@ def import_fsspec(importer): return fsspec -def import_ruyaml(importer): - with missing_package_raise("ruyaml", importer): - import ruyaml - return ruyaml +def import_ruamel(importer): + with missing_package_raise("ruamel.yaml", importer): + import ruamel.yaml + return ruamel.yaml def import_reconplogger(importer): diff --git a/jsonargparse/_type_checking.py b/jsonargparse/_type_checking.py index 11873d22..d1a2cab5 100644 --- a/jsonargparse/_type_checking.py +++ b/jsonargparse/_type_checking.py @@ -5,12 +5,12 @@ "ActionsContainer", "ArgumentParser", "docstring_parser", - "ruyamlCommentedMap", + "ruamelCommentedMap", ] if TYPE_CHECKING: # pragma: no cover import docstring_parser - from ruyaml.comments import CommentedMap as ruyamlCommentedMap + from ruamel.yaml.comments import CommentedMap as ruamelCommentedMap from ._core import ActionsContainer, ArgumentGroup, ArgumentParser else: diff --git a/jsonargparse_tests/test_cli.py b/jsonargparse_tests/test_cli.py index 53cd67d5..00093458 100644 --- a/jsonargparse_tests/test_cli.py +++ b/jsonargparse_tests/test_cli.py @@ -17,7 +17,7 @@ from jsonargparse import CLI, auto_cli, auto_parser, capture_parser, lazy_instance from jsonargparse._namespace import Namespace -from jsonargparse._optionals import docstring_parser_support, ruyaml_support +from jsonargparse._optionals import docstring_parser_support, ruamel_support from jsonargparse.typing import final from jsonargparse_tests.conftest import json_or_yaml_dump, json_or_yaml_load, skip_if_docstring_parser_unavailable @@ -351,7 +351,7 @@ def test_function_and_class_print_config_before_subcommands(): @skip_if_docstring_parser_unavailable -@pytest.mark.skipif(not ruyaml_support, reason="ruyaml not installed") +@pytest.mark.skipif(not ruamel_support, reason="ruamel.yaml package is required") def test_function_and_class_print_config_comments(): out = get_cli_stdout([cmd1, Cmd2, cmd3], args=["--print_config=comments", "Cmd2", "method2"]) assert "# Description of Cmd2" in out diff --git a/jsonargparse_tests/test_core.py b/jsonargparse_tests/test_core.py index d272bd62..59cbfb40 100644 --- a/jsonargparse_tests/test_core.py +++ b/jsonargparse_tests/test_core.py @@ -27,7 +27,7 @@ ) from jsonargparse._formatters import get_env_var from jsonargparse._namespace import NSKeyError -from jsonargparse._optionals import jsonnet_support, jsonschema_support, pyyaml_available, ruyaml_support +from jsonargparse._optionals import jsonnet_support, jsonschema_support, pyyaml_available, ruamel_support from jsonargparse.typing import Path_fc, Path_fr, path_type from jsonargparse_tests.conftest import ( capture_logs, @@ -779,7 +779,7 @@ def test_print_config_skip_null(print_parser): assert json_or_yaml_load(out) == {"g1": {"v2": "2"}, "g2": {}, "v1": 1} -@pytest.mark.skipif(not ruyaml_support, reason="ruyaml package is required") +@pytest.mark.skipif(not ruamel_support, reason="ruamel.yaml package is required") @skip_if_docstring_parser_unavailable def test_print_config_comments(print_parser): help_str = get_parser_help(print_parser) @@ -790,7 +790,7 @@ def test_print_config_comments(print_parser): assert "# Option v2. (default: 2)" in out -@pytest.mark.skipif(ruyaml_support, reason="ruyaml package should not be installed") +@pytest.mark.skipif(ruamel_support, reason="ruamel.yaml package should not be installed") def test_print_config_comments_unavailable(print_parser): help_str = get_parser_help(print_parser) assert "comments," not in help_str diff --git a/jsonargparse_tests/test_deprecated.py b/jsonargparse_tests/test_deprecated.py index 8dd05603..6c0560d2 100644 --- a/jsonargparse_tests/test_deprecated.py +++ b/jsonargparse_tests/test_deprecated.py @@ -41,10 +41,10 @@ from jsonargparse._optionals import ( docstring_parser_support, get_docstring_parse_options, - import_ruyaml, + import_ruamel, jsonnet_support, pyyaml_available, - ruyaml_support, + ruamel_support, url_support, ) from jsonargparse._util import argument_error @@ -743,14 +743,14 @@ def test_namespace_to_dict(): ) -@pytest.mark.skipif(not ruyaml_support, reason="ruyaml package is required") +@pytest.mark.skipif(not ruamel_support, reason="ruamel.yaml package is required") def test_DefaultHelpFormatter_yaml_comments(parser): parser.add_argument("--arg", type=int, help="Description") formatter = DefaultHelpFormatter(prog="test") from jsonargparse._common import parent_parser parent_parser.set(parser) - ruyaml = import_ruyaml("test_DefaultHelpFormatter_yaml_comments") + ruyaml = import_ruamel("test_DefaultHelpFormatter_yaml_comments") yaml = ruyaml.YAML() cfg = yaml.load("arg: 1") diff --git a/jsonargparse_tests/test_optionals.py b/jsonargparse_tests/test_optionals.py index 5a630ac5..afe0126e 100644 --- a/jsonargparse_tests/test_optionals.py +++ b/jsonargparse_tests/test_optionals.py @@ -16,10 +16,10 @@ import_jsonnet, import_jsonschema, import_requests, - import_ruyaml, + import_ruamel, jsonnet_support, jsonschema_support, - ruyaml_support, + ruamel_support, url_support, ) from jsonargparse.typing import is_final_class @@ -126,19 +126,19 @@ def test_fsspec_support_false(): ctx.match("test_fsspec_support_false") -# ruyaml support +# ruamel.yaml support -@pytest.mark.skipif(not ruyaml_support, reason="ruyaml package is required") -def test_ruyaml_support_true(): - import_ruyaml("test_ruyaml_support_true") +@pytest.mark.skipif(not ruamel_support, reason="ruamel.yaml package is required") +def test_ruamel_support_true(): + import_ruamel("test_ruamel_support_true") -@pytest.mark.skipif(ruyaml_support, reason="ruyaml package should not be installed") -def test_ruyaml_support_false(): +@pytest.mark.skipif(ruamel_support, reason="ruamel.yaml package should not be installed") +def test_ruamel_support_false(): with pytest.raises(ImportError) as ctx: - import_ruyaml("test_ruyaml_support_false") - ctx.match("test_ruyaml_support_false") + import_ruamel("test_ruamel_support_false") + ctx.match("test_ruamel_support_false") # config read mode tests diff --git a/pyproject.toml b/pyproject.toml index 2f6b2b1c..4e2ad45b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ all = [ "jsonargparse[toml]", "jsonargparse[urls]", "jsonargparse[fsspec]", - "jsonargparse[ruyaml]", + "jsonargparse[ruamel]", "jsonargparse[omegaconf]", "jsonargparse[typing-extensions]", "jsonargparse[reconplogger]", @@ -75,8 +75,11 @@ shtab = [ argcomplete = [ "argcomplete>=3.5.1", ] +ruamel = [ + "ruamel.yaml>=0.18.15", +] ruyaml = [ - "ruyaml>=0.20.0", + "jsonargparse[ruamel]", ] omegaconf = [ "omegaconf>=2.1.1", @@ -235,7 +238,7 @@ commands = [testenv:without-pyyaml] extras = test,coverage,all commands = - pip uninstall -y argcomplete omegaconf pyyaml reconplogger responses ruyaml types-PyYAML + pip uninstall -y argcomplete omegaconf pyyaml reconplogger responses ruamel.yaml ruamel.yaml.clib types-PyYAML python -m pytest {posargs} [testenv:without-future-annotations]