Anchored Entries in Unordered Sets Cannot Round-Trip
ruamel.yaml is a YAML 1.2 parser/emitter for Python
Brought to you by:
anthon
In a YAML Unordered Set, if any of the entries are Anchored, they are dumped back out lacking the required leading ? mark. This produces unusable YAML.
I am presently adding robust support for unordered sets to one of my projects and ran into this issue. As it is, this is now blocking project completion.
import sys
from ruamel.yaml import YAML
inp = """\
---
aliases:
- &bl_anchor Ty Cobb
baseball_legends: !!set
? Mark McGwire
? Sammy Sosa
? *bl_anchor
? Ken Griff
"""
yaml = YAML()
code = yaml.load(inp)
yaml.dump(code, sys.stdout)
As you can see, the code just loads the sample data and immediately dumps it back out.
The output is corrupt and cannot be loaded back in as YAML:
aliases:
- &bl_anchor Ty Cobb
baseball_legends: !!set
? Mark McGwire
? Sammy Sosa
*bl_anchor
? Ken Griff
Thank you so much for any and all help!
This is because the node's style is not passed to the alias event on emitting (and because that event doesn't take a style argument). The actual solution in code affects only two lines, but I am not sure how soon I can push out a new version.
To incorporate these changes in your code requires a lot more copying, but the following should get you going (I might have misses a few
ruamel.yaml.XXXX.prefixes, that are only necessary because of this code is extracted, so more extensive YAML might trigger an exception on an unknown Type, you can hopefully fix that yourself)Indeed, with the code above added to my project, several unit tests are now failing that had previously been passing. It looks like most are related to the new style attribute not present in other classes. I'll poke at it a bit to see if I can get everything passing again.
Thanks for the head start!
I'm lost "peeling the onion" with this; fixing one issue reveals more issues. The surface error from adding the above code is: AttributeError: 'MappingNode' object has no attribute 'style'.
So, I added the following to the patch code:
This caused an issue which I fixed by changing the
raise EmitterErrorcode to:However, this causes many more tests to fail with: ruamel.yaml.emitter.EmitterError: expected NodeEvent, but got DocumentEndEvent()
When fixing one issue causes dozens more, I'm doing something wrong.
I've narrowed the code causing the above EmitterError down to the following example:
Note that yamlpath.patches.aliasstyle is a copy-paste of your code above plus the adjustments I listed along with "just enough" documentation to make PEP257 happy. I've attached the complete file as aliasstyle.py.
I forgot to mention: In the attached aliasstyle.py above, I commented out the following line so you can see the original error which led me down the path described above.
Just uncomment that line to get the followup error.
I'll try to look at this today. I couldn't run all the normal tests yesterday evening as I have not fully finished setting up my forced distro switch (including missing
tox)I update the one line:
to
That way it doesn't barf anymore on MappingNode not having a style, and passes the testsuite for Python 3.9 and 3.8 (although 3.5-3.7 are installed, tox for some reason doesn't find them, so I still cannot fully test and don't want to push a new version until I can).
Last edit: Anthon van der Neut 2021-05-30
That was it! All of my unit tests are passing and the issue with Aliased entries in Unordered Sets is resolved with this workaround. Until your next release -- and I do hope the tox issue turns out to be something simple -- I am no longer blocked, thanks to your help. Please feel free to de-prioritize this issue to major. Thank you, so much!!
I pushed 0.17.5. What you might not have noticed is that there was spurious (non-significant) space after the
? *bl_anchorin your output. That is not fixed in the code above, but it is fixed in 0.17.5.Quick follow-up: I'm unable to use 0.17.5 because my tests won't complete with it. It looks like a couple of Type signatures are misaligned with their signatures:
mypy errors:
I know you were having some issues getting your test suite to run. I hope the above can help in some way.
Can you try with 0.17.6 ?
Most surprisingly, 0.17.6 and 0.17.7 both cause MYPY to become more strict with regard to my uses of CommentedMap and CommentedSeq. I verified by downgrading ruamel.yaml and running the same test suite; the strictness relaxed. Upgrading to 0.17.6+ has forced me to more strictly type my own variables wherever I'm interacting with CommentedMap and CommentedSeq. I'm not saying this is bad; it's just a surprising side-effect. I don't mind the stricter typing.
That said, all 1,188 of my unit tests, plus MYPY and PyLint, pass with both 0.17.6 and 0.17.7. The runs can be seen at: https://github.com/wwkimball/yamlpath/actions/workflows/python-publish-to-test-pypi.yml
[I had individually linked the two runs but "SpamBot protection activated!" for this SourceForge thread banned me from posting more than one link.]
Thank you for all of your help!!
I had to install mypy on my new distro and got the latest version and ~3 hours work of getting rid of messages, in the process I might have changed things so you had to be more strict. I didn't do that on purpose, just a side effect of trying to get rid of messages.
One thing that happened is that flake8 complained about using a constant string in something like
if hasattr(self, 'value'): x= getattr(self, 'value')and after changing that toif hasattr(self, 'value'): x self.value, mypy complained about self not having 'value' as attribute... One year when I have nothing else to do I'll go over all the type definitions and get rid ofAny. It is probably easier if you add proper typing to your code from day one.Absolutely true! I fight the same battles from time to time, so I know your pain. I learned very early the same lesson, so you'll find I use types as religiously as I can bear to. You can quiet a lot of the mypy noise regarding returning
Anywith a mypy.ini entry like:I do the same for my project because I haven't spent enough time to zero-in on just what ruamel.yaml returns for many of its methods. :)