How to add a comment to a YAML file in Python

I am writing a YAML file using https://pypi.python.org/pypi/ruamel.yaml

The code is as follows:

import ruamel.yaml from ruamel.yaml.comments import CommentedSeq d = {} for m in ['B1', 'B2', 'B3']: d2 = {} for f in ['A1', 'A2', 'A3']: d2[f] = CommentedSeq(['test', 'test2']) if f != 'A2': d2[f].fa.set_flow_style() d[m] = d2 with open('test.yml', "w") as f: ruamel.yaml.dump( d, f, Dumper=ruamel.yaml.RoundTripDumper, default_flow_style=False, width=50, indent=8) 

I just want to add a comment from above:

 # Data for Class A 

Before YAML data.

+7
source share
2 answers

In your with block, you can write whatever you want to a file. Since you just need a comment at the top, add a call to f.write() before you call ruamel:

 with open('test.yml', "w") as f: f.write('# Data for Class A\n') ruamel.yaml.dump( d, f, Dumper=ruamel.yaml.RoundTripDumper, default_flow_style=False, width=50, indent=8) 
+5
source

In principle, this is possible because you can use such "start of file" comments in both directions, but this is not very well supported in the current ruamel.yaml 0.10 and, of course, not when starting from scratch (i.e. without changing existing file). Below is an easy and relatively good solution, but I would like to first introduce an ugly workaround and a step-by-step guide on how to do this.

Ugly
An ugly way to do this is to simply add a comment to the file before writing YAML data to it. That is, insert:

 f.write('# Data for Class A\n') 

just before ruamel.yaml.dump(...)

Step by step :
To insert a comment into the data structure so that the hack described above is not necessary, you first need to make sure that your d data is of type CommentedMap . If you compare the difference of this variable d with the one that has the comment by loading the commented out YAML back into c

 import ruamel.yaml from ruamel.yaml.comments import Comment, CommentedSeq, CommentedMap d = CommentedMap() # <<<<< most important for m in ['B1', 'B2', 'B3']: d2 = {} for f in ['A1', 'A2', 'A3']: d2[f] = CommentedSeq(['test', 'test2']) if f != 'A2': d2[f].fa.set_flow_style() d[m] = d2 yaml_str = ruamel.yaml.dump(d, Dumper=ruamel.yaml.RoundTripDumper, default_flow_style=False, width=50, indent=8) assert not hasattr(d, Comment.attrib) # no attribute on the CommentedMap comment = 'Data for Class A' commented_yaml_str = '# ' + comment + '\n' + yaml_str c = ruamel.yaml.load(commented_yaml_str, Loader=ruamel.yaml.RoundTripLoader) assert hasattr(c, Comment.attrib) # c has the attribute print c.ca # and this is what it looks like print d.ca # accessing comment attribute creates it empty assert hasattr(d, Comment.attrib) # now the CommentedMap has the attribute 

This prints:

 Comment(comment=[None, [CommentToken(value=u'# Data for Class A\n')]], items={}) Comment(comment=None, items={}) 

Comment has an attribute attribute comment that must be set in a list of 2 elements, which consists of an EOL comment (always only one) and a list of comments on the previous line (in the CommentTokens form)

To create a CommentToken, you need a (fake) StartMark that tells which column it starts with:

 from ruamel.yaml.error import StreamMark start_mark = StreamMark(None, None, None, 0, None, None) # column 0 

Now you can create a token:

 from ruamel.yaml.tokens import CommentToken ct = CommentToken('# ' + comment + '\n', start_mark, None) 

Assign the token as the first element of the previous list in your CommentedMap:

 d.ca.comment = [None, [ct]] print d.ca # in case you want to check 

gives you:

 Comment(comment=[None, [CommentToken(value='# Data for Class A\n')]], items={}) 

And finally:

 print ruamel.yaml.dump(d, Dumper=ruamel.yaml.RoundTripDumper) 

gives:

 # Data for Class A B1: A1: [test, test2] A3: [test, test2] A2: - test - test2 B2: A1: [test, test2] A3: [test, test2] A2: - test - test2 B3: A1: [test, test2] A3: [test, test2] A2: - test - test2 

Of course, you do not need to create an object c , this is just for illustration.

What you should use : To make the whole exercise a little easier, you can just forget about the details and the patch in the following method for CommentedBase once:

 from ruamel.yaml.comments import CommentedBase def set_start_comment(self, comment, indent=0): """overwrites any preceding comment lines on an object expects comment to be without '#' and possible have mutlple lines """ from ruamel.yaml.error import StreamMark from ruamel.yaml.tokens import CommentToken if self.ca.comment is None: pre_comments = [] self.ca.comment = [None, pre_comments] else: pre_comments = self.ca.comments[1] if comment[-1] == '\n': comment = comment[:-1] # strip final newline if there start_mark = StreamMark(None, None, None, indent, None, None) for com in comment.split('\n'): pre_comments.append(CommentToken('# ' + com + '\n', start_mark, None)) if not hasattr(CommentedBase, 'set_start_comment'): # in case it is there CommentedBase.set_start_comment = set_start_comment 

and then just do:

 d.set_start_comment('Data for Class A') 
+3
source

Source: https://habr.com/ru/post/989573/


All Articles