Controlling the style of dumped YAML using PyYAML

I had a list of Python dictionaries I wanted to output as YAML, but I wanted to control the style of the output.

Here's the data:

items = [
    {
        "date": "2020-11-28",
        "body": "[Datasette 0.52](https://docs.datasette.io/en/stable/changelog.html#v0-52) - `--config` is now `--setting`, new `database_actions` plugin hook, `datasette publish cloudrun --apt-get-install` option and several bug fixes.",
    },
    {
        "date": "2020-10-31",
        "body": "[Datasette 0.51](https://docs.datasette.io/en/stable/changelog.html#v0-51) - A new visual design, plugin hooks for adding navigation options, better handling of binary data, URL building utility methods and better support for running Datasette behind a proxy. [Annotated release notes](https://simonwillison.net/2020/Nov/1/datasette-0-51/).",
    },
]

By default, the YAML output by import yaml; print(yaml.dump(items)) looks like this:

- body: '[Datasette 0.52](https://docs.datasette.io/en/stable/changelog.html#v0-52)
    - `--config` is now `--setting`, new `database_actions` plugin hook, `datasette
    publish cloudrun --apt-get-install` option and several bug fixes.'
  date: '2020-11-28'
- body: '[Datasette 0.51](https://docs.datasette.io/en/stable/changelog.html#v0-51)
    - A new visual design, plugin hooks for adding navigation options, better handling
    of binary data, URL building utility methods and better support for running Datasette
    behind a proxy. [Annotated release notes](https://simonwillison.net/2020/Nov/1/datasette-0-51/).'
  date: '2020-10-31'

I wanted to list the date key first, and I wanted the body key to use >- YAML multi-line syntax rather than a single quoted string.

I ended up combining these two recipes from Stack Overflow. First I registered new representers with PyYaml:

import yaml
from collections import OrderedDict

class literal(str):
    pass

def literal_presenter(dumper, data):
    return dumper.represent_scalar("tag:yaml.org,2002:str", data, style=">")


yaml.add_representer(literal, literal_presenter)

def represent_ordereddict(dumper, data):
    value = []

    for item_key, item_value in data.items():
        node_key = dumper.represent_data(item_key)
        node_value = dumper.represent_data(item_value)

        value.append((node_key, node_value))

    return yaml.nodes.MappingNode(u"tag:yaml.org,2002:map", value)

yaml.add_representer(OrderedDict, represent_ordereddict)

Then I used the following Python code to output my YAML in the desired key order:

print(yaml.dump([OrderedDict([
    ("date", item["date"]),
    ("body", literal(item["body"]))
]) for item in items], width=100))

The result was:

- date: '2020-11-28'
  body: >-
    [Datasette 0.52](https://docs.datasette.io/en/stable/changelog.html#v0-52) - `--config` is now `--setting`,
    new `database_actions` plugin hook, `datasette publish cloudrun --apt-get-install` option and several
    bug fixes.
- date: '2020-10-31'
  body: >-
    [Datasette 0.51](https://docs.datasette.io/en/stable/changelog.html#v0-51) - A new visual design,
    plugin hooks for adding navigation options, better handling of binary data, URL building utility methods
    and better support for running Datasette behind a proxy. [Annotated release notes](https://simonwillison.net/2020/Nov/1/datasette-0-51/).

Using > as the line style caused the width=100 argument to be respected. When I tried this with | as the line style the indentation was not applied.

Created 2020-12-07T12:48:39-08:00, updated 2020-12-07T12:53:40-08:00 · History · Edit