Search code examples
pythonpython-2.7yamlpyyamlruamel.yaml

How to preserve indenting in key-value when dumping yaml


How to preserve indenting in key-value when dumping yaml ? I am using ruamel yaml

Code:

in_str='''Pets:
    Cat:
       Tom
    Mouse:
       Jerry
    Dog:
       Scooby
       '''


import ruamel.yaml, sys
results = ruamel.yaml.load(in_str, ruamel.yaml.RoundTripLoader, preserve_quotes=True)
results['Pets']['Bird']='Tweety'
ruamel.yaml.dump(results, sys.stdout, ruamel.yaml.RoundTripDumper, default_flow_style=True,indent=2, block_seq_indent=2)

Output :

Pets:
  Cat: Tom
  Mouse: Jerry
  Dog: Scooby
  Bird: Tweety

Expected Output:

Pets:
    Cat:
       Tom
    Mouse:
       Jerry
    Dog:
       Scooby
    Bird:
       Tweety

Solution

  • In order to achieve this you'll have to hook into the Emitter and have it insert a newline and appropriate indentation when processing a mapping value. This can be done with the old style API that you use, but is better done with the new ruamel.yaml API, which gives you e.g. the possibility to indent sequences and mappings with different values:

    import sys
    import ruamel.yaml
    from ruamel.yaml.emitter import Emitter
    
    class MyEmitter(Emitter):
        def expect_block_mapping_simple_value(self):
            # type: () -> None
            if getattr(self.event, 'style', None) != '?':
                # prefix = u''
                if self.indent == 0 and self.top_level_colon_align is not None:
                    # write non-prefixed colon
                    c = u' ' * (self.top_level_colon_align - self.column) + self.colon
                else:
                    c = self.prefixed_colon
                self.write_indicator(c, False)
                # the next four lines force a line break and proper indent of the value
                self.write_line_break()
                self.indent += self.best_map_indent
                self.write_indent()
                self.indent -= self.best_map_indent
            self.states.append(self.expect_block_mapping_key)
            self.expect_node(mapping=True)
    
    
    in_str='''\
    Pets:
        Cat:
           Tom
        Mouse:
           - Jerry
           - 'Mickey'
        Dog:
           Scooby
           '''
    
    yaml = ruamel.yaml.YAML()
    yaml.Emitter = MyEmitter
    yaml.indent(mapping=4, sequence=2, offset=0)
    yaml.preserve_quotes = True
    results = yaml.load(in_str)
    results['Pets']['Bird']='Tweety'
    yaml.dump(results, sys.stdout)
    

    this gives:

    Pets:
        Cat:
            Tom
        Mouse:
            - Jerry
            - 'Mickey'
        Dog:
            Scooby
        Bird:
            Tweety
    

    Things to note:

    • You only have to handle simple scalar values (as opposed to mappings/sequences, which are already "pushed" to the next line in block sequence "mode")
    • The expect_block_mapping_simple_value is copied from the source and a few lines added. There is currently no "hook" to do this without duplicating some code.
    • You can play with the sequence and offset values for yaml.indent() if you have sequences and need different indentation for that.
    • All of this assumes consistent indenting, individual indentation is not preserved (i.e. if some values are indented with more or less than the four positions, they'll end up with 4 positions).