Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Changed
gives a more readable output. Use the new ``json_compact`` format for the
previous single line output (`#970
<https://github.com/mauvilsa/jsonargparse/pull/970>`__).
- The ``yaml`` dump format now writes multi-line strings as literal blocks, i.e.
``|``, instead of escaping the line breaks (`#972
<https://github.com/mauvilsa/jsonargparse/pull/972>`__).

Removed
^^^^^^^
Expand Down
7 changes: 5 additions & 2 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1424,8 +1424,11 @@ 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_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``:
parser. The ``yaml`` format dumps with a subclass of `yaml.SafeDumper
<https://pyyaml.org/wiki/PyYAMLDocumentation#dumper>`__ that writes multi-line
strings as literal blocks, i.e. ``|``, instead of escaping the line breaks. More
formats are added with :func:`.set_dumper`, for example to dump with PyYAML's
``default_flow_style``:

.. testcode::

Expand Down
24 changes: 23 additions & 1 deletion jsonargparse/_loaders_dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

not_loaded = object()
yaml_default_loader = None
yaml_default_dumper = None


def load_basic(value):
Expand Down Expand Up @@ -246,9 +247,30 @@ def replace_unset(data):
return data


def get_yaml_default_dumper():
global yaml_default_dumper
if yaml_default_dumper:
return yaml_default_dumper

yaml = import_pyyaml("get_yaml_default_dumper")

class DefaultDumper(yaml.SafeDumper):
pass

def represent_str(dumper, data):
# literal block style for multiline strings, unless not representable as such
style = "|" if "\n" in data else None
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style=style)

DefaultDumper.add_representer(str, represent_str)

yaml_default_dumper = DefaultDumper
return yaml_default_dumper


def yaml_dump(data):
yaml = import_pyyaml("yaml_dump")
return yaml.safe_dump(data, **dump_yaml_kwargs)
return yaml.dump(data, Dumper=get_yaml_default_dumper(), **dump_yaml_kwargs)


def yaml_comments_dump(data, parser):
Expand Down
27 changes: 27 additions & 0 deletions jsonargparse_tests/test_loaders_dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,33 @@ def test_set_loader_parser_mode_subparsers(parser, subparser):
assert "custom" == subparser.parser_mode


@skip_if_no_pyyaml
def test_dump_yaml_multiline_string(parser):
parser.add_argument("--text", type=str)
cfg = parser.parse_args(["--text=first line\nsecond line\n"])
dump = parser.dump(cfg)
assert dump == "text: |\n first line\n second line\n"
assert json_or_yaml_load(dump) == {"text": "first line\nsecond line\n"}


@skip_if_no_pyyaml
def test_dump_yaml_multiline_string_no_trailing_newline(parser):
parser.add_argument("--text", type=str)
cfg = parser.parse_args(["--text=first line\nsecond line"])
dump = parser.dump(cfg)
assert dump == "text: |-\n first line\n second line\n"
assert json_or_yaml_load(dump) == {"text": "first line\nsecond line"}


@skip_if_no_pyyaml
def test_dump_yaml_multiline_string_block_not_possible(parser):
parser.add_argument("--text", type=str)
cfg = parser.parse_args(["--text=trailing space \nsecond line\n"])
dump = parser.dump(cfg)
assert dump == 'text: "trailing space \\nsecond line\\n"\n'
assert json_or_yaml_load(dump) == {"text": "trailing space \nsecond line\n"}


@skip_if_no_pyyaml
def test_dump_header_yaml(parser):
parser.add_argument("--int", type=int, default=1)
Expand Down
13 changes: 13 additions & 0 deletions jsonargparse_tests/test_yaml_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ def block(text: str, depth: int = 0) -> str:
return indent(dedent(text), " " * depth)


def test_dump_comments_multiline_string(parser):
parser.add_argument("--text", type=str, help="Some text.")
dump = get_dump(parser, ["--text=first line\nsecond line\n"])
assert dump == block(
"""
# Some text. (type: str, default: null)
text: |
first line
second line
"""
)


class Optimizer:
def __init__(self, lr: float = 0.1):
"""Base optimizer.
Expand Down