最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Setting yaml style for specific keys via custom representer - Stack Overflow

programmeradmin2浏览0评论

I wish to set some specific key values, all of which are lists, to be flow style and everything else to block style.

I can see that I can set the style of any specific CommentedSeq via CommentedSeq.fa.set_flow_style() which could be done post-loading the yaml data but I'd rather set up a custom representer to handle this.

The following function seems to work post-loading:

from ruamel.yaml import YAML
from ruamel.yamlments import CommentedMap, CommentedSeq
import sys

# Keys to enforce flow style for
keys_with_flow_style = ["categories", "classification"]

def set_flow_style_recursive(yamldata, flowkeys):
    if isinstance(yamldata, (dict, CommentedMap)):
        for k,v in yamldata.items():
            if k in flowkeys:
                if isinstance(v, (dict, CommentedMap)):
                    set_flow_style_recursive(v, flowkeys)
                elif isinstance(v, (CommentedSeq)):
                    v.fa.set_flow_style()
                    set_flow_style(v, flowkeys)
            elif isinstance(v, (dict, CommentedMap)):
                set_flow_style_recursive(v, flowkeys)
    elif isinstance(yamldata, (list, CommentedSeq)):
        for item in yamldata:
            set_flow_style_recursive(item, flowkeys)

yaml_data = """
data:
  categories: 
    - CLNT
    - TXN
    - MKDT
    - REFD
    - PERS
    - CODE
  classification: 
    - RES
  description: This application processes data
"""

yaml = YAML().load(yaml_data)
set_flow_style_recursive(yaml, keys_with_flow_style)
YAML().dump(yaml, syst.stdout)

It prints :

"""
data:
  categories: [CLNT, TXN, MKDT, REFD, PERS, CODE]
  classification: [RES]
  description: This application processes data
"""

What I can't figure out is how to turn this into a Representer so the flow style is applied to specific key-values automatically on loading (and by default everything else is block style). Is anyone here familiar with that?

I wish to set some specific key values, all of which are lists, to be flow style and everything else to block style.

I can see that I can set the style of any specific CommentedSeq via CommentedSeq.fa.set_flow_style() which could be done post-loading the yaml data but I'd rather set up a custom representer to handle this.

The following function seems to work post-loading:

from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedMap, CommentedSeq
import sys

# Keys to enforce flow style for
keys_with_flow_style = ["categories", "classification"]

def set_flow_style_recursive(yamldata, flowkeys):
    if isinstance(yamldata, (dict, CommentedMap)):
        for k,v in yamldata.items():
            if k in flowkeys:
                if isinstance(v, (dict, CommentedMap)):
                    set_flow_style_recursive(v, flowkeys)
                elif isinstance(v, (CommentedSeq)):
                    v.fa.set_flow_style()
                    set_flow_style(v, flowkeys)
            elif isinstance(v, (dict, CommentedMap)):
                set_flow_style_recursive(v, flowkeys)
    elif isinstance(yamldata, (list, CommentedSeq)):
        for item in yamldata:
            set_flow_style_recursive(item, flowkeys)

yaml_data = """
data:
  categories: 
    - CLNT
    - TXN
    - MKDT
    - REFD
    - PERS
    - CODE
  classification: 
    - RES
  description: This application processes data
"""

yaml = YAML().load(yaml_data)
set_flow_style_recursive(yaml, keys_with_flow_style)
YAML().dump(yaml, syst.stdout)

It prints :

"""
data:
  categories: [CLNT, TXN, MKDT, REFD, PERS, CODE]
  classification: [RES]
  description: This application processes data
"""

What I can't figure out is how to turn this into a Representer so the flow style is applied to specific key-values automatically on loading (and by default everything else is block style). Is anyone here familiar with that?

Share Improve this question edited Jan 20 at 9:19 pinkfloydfan asked Jan 20 at 8:48 pinkfloydfanpinkfloydfan 534 bronze badges 1
  • Yes someone is familiar with that (given that is the only real answer to your question, you might want to rethink future questions here). The block style is not the default. If you load data from a YAML document, the block/flow style of a collection is preserved. Only collection types (list, dict) added by the program default to block style (when using YAML() or YAML(typ='rt')) – Anthon Commented Jan 20 at 13:38
Add a comment  | 

1 Answer 1

Reset to default 0

Representing in YAML is used to go from data to YAML, i.e. during dumping. You indicate you want to do this during loading, so that would require an alternative constructor.

The alternative constructor would have to be for the YAML mapping (to Python dict), as during construction of the list from the YAML sequence, you don't have the information about whether that list is going to be a value for a dictionary key (at least not without adjusting the mapping constructor as well).

IMO the best place to put this is in the method creating the Python dict: RoundTripConstructor.construct_yaml_map() which is relatively small. You could alternativey change the method .construct_mapping() it uses, which would prevent you from having to run over the keys of the dict afterwards to find a match. But that method is much longer, resulting in more copied code (and a greater chance it changes with a new release).

import sys
import ruamel.yaml

yaml_str = """\
data:
  categories:
    - CLNT
    - TXN
    - MKDT
    - REFD
    - PERS
    - CODE
  classification:
    - RES
  description: This application processes data
"""

class FlowStyleValueCreator:
    def __init__(self, *args):
        self._flow_style_value_keys = args
        ruamel.yaml.constructor.RoundTripConstructor.add_constructor('tag:yaml.org,2002:map', self.construct_yaml_map)

    def construct_yaml_map(self, constructor, node):
        data = ruamel.yaml.comments.CommentedMap()
        data._yaml_set_line_col(node.start_mark.line, node.start_mark.column)
        yield data
        constructor.construct_mapping(node, data, deep=True)
        for k, v in data.items():
            if k in self._flow_style_value_keys and isinstance(v, ruamel.yaml.comments.CommentedSeq):
                v.fa.set_flow_style()
        constructor.set_collection_style(data, node)

fsvc = FlowStyleValueCreator('categories', 'classification')

yaml = ruamel.yaml.YAML()
yaml.indent(sequence=4, offset=2)
yaml.preserve_quotes = True
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

which gives:

data:
  categories: [CLNT, TXN, MKDT, REFD, PERS, CODE]
  classification: [RES]
  description: This application processes data

Which looks like your output except for the surrounding tripe-quotes (which I think your code does not actually print).

The yaml.indent() is there to keep the extra indentation your block style sequences have (in case they are not forced to flow style).

The yield in construct_yaml_map() is there to handle self-recursive data structures (using YAML anchors/aliases)

发布评论

评论列表(0)

  1. 暂无评论