本质上,您调整加载器,以加载标记(标量)对象,就好像它们是映射一样,标签是键,值是标量。但是您没有做任何事情来区分dict
从这样的映射加载的映射与从正常映射加载的其他字典,也没有任何特定的代码来表示这样的映射以“取回标签”。
当您尝试“创建”带有标签的标量时,您只需创建一个以感叹号开头的字符串,并且需要将其转储引用以将其与真正的标记节点区分开来。
使这一切变得模糊的是,您的示例通过分配来覆盖加载的数据,base["foo"]
因此您可以从safe_load
和之前的所有代码派生的唯一内容是它不会引发异常。即,如果您省略以输出开头的行,base["foo"] = {
则如下所示:
foo:
aa:
Ref: bb
bar:
Ref: barr
这与Ref: bb
普通的转储字典无法区分。如果您想探索这条路线,那么您应该创建一个子类TagDict(dict)
,并funcparse
返回该子类,并为该子类添加一个 representer
从键重新创建标签然后转储值的子类。一旦可行(往返等于输入),您可以执行以下操作:
"RouteTableId" : TagDict('Ref', 'aaa')
如果这样做,除了删除未使用的库之外,还应该更改代码以关闭代码中的文件指针txt
,因为这可能会导致问题。您可以使用以下with
语句优雅地做到这一点:
with open("/space/tmp/a.template","r") as txt:
base = ruamel.yaml.safe_load(txt)
(我也将省略"r"
(或在它前面放一个空格);并txt
用更合适的变量名替换,表明这是一个(输入)文件指针)。
您的 中也有'Split'
两次条目funcnames
,这是多余的。
一个更通用的解决方案可以通过使用multi-constructor
匹配任何标签并具有三种基本类型来涵盖标量、映射和序列来实现。
import sys
import ruamel.yaml
yaml_str = """\
foo:
scalar: !Ref barr
mapping: !Select
a: !Ref 1
b: !Base64 A413
sequence: !Split
- !Ref baz
- !Split Multi word scalar
"""
class Generic:
def __init__(self, tag, value, style=None):
self._value = value
self._tag = tag
self._style = style
class GenericScalar(Generic):
@classmethod
def to_yaml(self, representer, node):
return representer.represent_scalar(node._tag, node._value)
@staticmethod
def construct(constructor, node):
return constructor.construct_scalar(node)
class GenericMapping(Generic):
@classmethod
def to_yaml(self, representer, node):
return representer.represent_mapping(node._tag, node._value)
@staticmethod
def construct(constructor, node):
return constructor.construct_mapping(node, deep=True)
class GenericSequence(Generic):
@classmethod
def to_yaml(self, representer, node):
return representer.represent_sequence(node._tag, node._value)
@staticmethod
def construct(constructor, node):
return constructor.construct_sequence(node, deep=True)
def default_constructor(constructor, tag_suffix, node):
generic = {
ruamel.yaml.ScalarNode: GenericScalar,
ruamel.yaml.MappingNode: GenericMapping,
ruamel.yaml.SequenceNode: GenericSequence,
}.get(type(node))
if generic is None:
raise NotImplementedError('Node: ' + str(type(node)))
style = getattr(node, 'style', None)
instance = generic.__new__(generic)
yield instance
state = generic.construct(constructor, node)
instance.__init__(tag_suffix, state, style=style)
ruamel.yaml.add_multi_constructor('', default_constructor, Loader=ruamel.yaml.SafeLoader)
yaml = ruamel.yaml.YAML(typ='safe', pure=True)
yaml.default_flow_style = False
yaml.register_class(GenericScalar)
yaml.register_class(GenericMapping)
yaml.register_class(GenericSequence)
base = yaml.load(yaml_str)
base['bar'] = {
'name': 'abc',
'Resources': {
'RouteTableId' : GenericScalar('!Ref', 'aaa'),
'VpcPeeringConnectionId' : GenericScalar('!Ref', 'bbb'),
'yourname': 'dfw',
's' : GenericSequence('!Split', ['a', GenericScalar('!Not', 'b'), 'c']),
}
}
yaml.dump(base, sys.stdout)
输出:
bar:
Resources:
RouteTableId: !Ref aaa
VpcPeeringConnectionId: !Ref bbb
s: !Split
- a
- !Not b
- c
yourname: dfw
name: abc
foo:
mapping: !Select
a: !Ref 1
b: !Base64 A413
scalar: !Ref barr
sequence: !Split
- !Ref baz
- !Split Multi word scalar
请注意,序列和映射被正确处理并且它们也可以被创建。然而,没有检查:
- 您提供的标签实际上是有效的
- 与标签关联的值是该标签名称的正确类型(标量、映射、序列)
- 如果您想
GenericMapping
表现得更像dict
,那么您可能希望它是dict
(而不是Generic
)的子类并提供适当的__init__
(同上GenericSequence
/ list
)
当作业更改为更接近您的作业时:
base["foo"] = {
"name": "abc",
"Resources": {
"RouteTableId" : GenericScalar('!Ref', 'aaa'),
"VpcPeeringConnectionId" : GenericScalar('!Ref', 'bbb'),
"yourname": "dfw"
}
}
输出是:
foo:
Resources:
RouteTableId: !Ref aaa
VpcPeeringConnectionId: !Ref bbb
yourname: dfw
name: abc
这正是您想要的输出。