Can ruamel.yaml encode an enum?
Asked Answered
L

2

5

The following is not working for me, with Python 3.4.7, ruamel.yaml version 0.15.35:

import sys
import enum

import ruamel.yaml
from ruamel.yaml import yaml_object
yaml = ruamel.yaml.YAML()

@yaml_object(yaml)
class Speed(enum.IntEnum):
    Reverse = 0
    Neutral = 1
    Low = 2
    Drive = 3
    Park = 999

print("Neutral:", repr(Speed.Neutral))

yaml.dump(Speed.Neutral, sys.stdout)

I get a totally reasonable repr:

Neutral: <Speed.Neutral: 1>

but the .dump() raises:

ruamel.yaml.representer.RepresenterError: cannot represent an object: <enum 'Speed'>

If enum's are not supported, is there something I can do to extend the enum class I am using (or the subclass enum.IntEnum I have created), e.g. a dunder method?

Ludeman answered 29/12, 2017 at 4:14 Comment(0)
P
9

enum is not supported out-of-the-box, essentially because the default dump method is safe and therefore doesn't support arbitrary Python objects. That safety excludes types as enum from the standard library as well.

What you should do is add a to_yaml classmethod to your Speed class as described in the ruamel.yaml documentation:

import sys
import enum

import ruamel.yaml
from ruamel.yaml import yaml_object
yaml = ruamel.yaml.YAML()

@yaml_object(yaml)
class Speed(enum.IntEnum):
    Reverse = 0
    Neutral = 1
    Low = 2
    Drive = 3
    Park = 999

    @classmethod
    def to_yaml(cls, representer, node):
        return representer.represent_scalar(
            u'!Speed',
            '{}-{}'.format(node._name_, node._value_)
        )

yaml.dump(Speed.Neutral, sys.stdout)

which gives:

!Speed Neutral-1
...

You can of course adapt the string representation to your liking (and add a from_yaml to be able to load the output back in).

Please note that you cannot add yaml_tag as in the documentation example, as this would interfere with the enum values.

Poltroonery answered 29/12, 2017 at 7:25 Comment(3)
and what if we don't have access to the class. E.g. what if we consume data from a 3rd party lib? Do we have to convert the enum to a string/integer manually?Irreparable
If you don't have access to a class, you cannot use it, so there is never need to dump it. If you have access to a class, but cannot update/extend the .py it is in, there are other methods of making that class "known" to ruamel.yaml. That would be an excellent question for Stack Overflow, but not an appropriate comment.Poltroonery
I get notified via email of any new question tagged ruamel.yaml, no need to notify me via a comment for that.Poltroonery
F
1

I think you'll need to pass the enum by value.

https://docs.python.org/3/library/enum.html#programmatic-access-to-enumeration-members-and-their-attributes

yaml.dump(Speed.Neutral.value, sys.stdout)

Extended answer to add a @classmethod which calls .value as it's being passed

http://yaml.readthedocs.io/en/latest/dumpcls.html

import sys
import enum
import ruamel.yaml
from ruamel.yaml import yaml_object
yaml = ruamel.yaml.YAML()

@yaml_object(yaml)
class Speed(enum.IntEnum):
    Reverse = 0
    Neutral = 1
    Low = 2
    Drive = 3
    Park = 999

    @classmethod
    def to_yaml(cls, representer, node):
        return representer.represent_scalar(u'!enum:', u'{.value}'.format(node))
Flaxseed answered 29/12, 2017 at 4:40 Comment(1)
That is a workaround, but it loses the type information, as I am then just encoding an int. YAML is able to preserve type and value for other objects. It feels like it should be able to do so for enums.Ludeman

© 2022 - 2024 — McMap. All rights reserved.