Search code examples
pythonyamlpyyamlruamel.yaml

Outputting YAML with Python: incorrect formatting without lists in input


I'm trying to create YAML from Python dictionaries. So far I've tried both PyYAML and ruamel.yaml and both have the same result: if the input dictionary does not contain a list, the output is not formatted correctly.

Here's the script:

from ruamel import yaml
import sys

yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'armament': ['photon torpedoes','phasers'], 'number': 1701}, sys.stdout)
print('\n')
yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'number': 1701}, sys.stdout)

And here's the output:

armament: [photon torpedoes, phasers]
class: Galaxy
name: Enterprise
number: 1701

{class: Galaxy, name: Enterprise, number: 1701}

The desired output is that the second YAML dump should be formatted like the first. What's going on here?


Solution

  • You are using the old style API and that defaults to output any leaf nodes in flow style. In your case both the list/sequece [photon torpedoes, phasers] and your seconds dump (where the root level is the leaf node).


    Assuming that you don't just want the explanation why this happens, but also how to change this behaviour, with the new API, using instances of ruamel.yaml.YAML, the default is to make everything flow-style, including all leaf nodes:

    from ruamel.yaml import YAML
    import sys
    
    yaml = YAML()
    
    yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'armament': ['photon torpedoes','phasers'], 'number': 1701}, sys.stdout)
    print()
    yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'number': 1701}, sys.stdout)
    

    giving:

    name: Enterprise
    class: Galaxy
    armament:
    - photon torpedoes
    - phasers
    number: 1701
    
    name: Enterprise
    class: Galaxy
    number: 1701
    

    Still not what you want :-)

    You now have two options to get what you indicated: leaf node flow-style on your first dump and leaf node block-style on your second.

    The first is to have one instance the default_flow_style set used for the first dump and the "normal" one for the second dump:

    from ruamel.yaml import YAML
    import sys
    
    yaml = YAML()
    
    yaml.default_flow_style = None
    yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'armament': ['photon torpedoes','phasers'], 'number': 1701}, sys.stdout)
    print()
    yaml = YAML()
    yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'number': 1701}, sys.stdout)
    

    which gives:

    name: Enterprise
    class: Galaxy
    armament: [photon torpedoes, phasers]
    number: 1701
    
    name: Enterprise
    class: Galaxy
    number: 1701
    

    The second option is to explicitly set the flow-style on the object that you want to output as sequence. Therefor you have to created a ruamel.yaml.comments.CommentedSeq instance, which is normally used when loading to preserve flow/block-style, comments, etc.:

    from ruamel.yaml import YAML, comments
    import sys
    
    yaml = YAML()
    
    armaments = comments.CommentedSeq(['photon torpedoes','phasers'])
    armaments.fa.set_flow_style()
    yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'armament': armaments, 'number': 1701}, sys.stdout)
    print()
    yaml.dump({'name': 'Enterprise', 'class': 'Galaxy', 'number': 1701}, sys.stdout)
    

    which also gives:

    name: Enterprise
    class: Galaxy
    armament: [photon torpedoes, phasers]
    number: 1701
    
    name: Enterprise
    class: Galaxy
    number: 1701
    

    This second option of course gives you fine control (there is also a CommentedMap) as you can have those objects at all levels of your data hierarchy, not just at the leaves that are collections.


    Please note that you don't have to go through any of these antics when loading the required output from a YAML file that has the formatting as you do want it. In that case the dict resp. list like instances are created with the right flow/block-style, so the output doesn't unexpectedly change when just changing/adding a value and dumping back.