Python ruamel.yaml转储带引号的标记
我试图使用ruamel.yaml使用python动态修改AWS CloudFormation模板。我添加了以下代码,以使安全加载与诸如Python ruamel.yaml转储带引号的标记,python,yaml,pyyaml,ruamel.yaml,Python,Yaml,Pyyaml,Ruamel.yaml,我试图使用ruamel.yaml使用python动态修改AWS CloudFormation模板。我添加了以下代码,以使安全加载与诸如之类的CloudFormation函数一起工作!Ref。但是,当我将它们转储出去时,这些值将被删除!Ref(或任何其他函数)将用引号括起来。CloudFormation无法识别这一点 见下例: import sys, json, io, boto3 import ruamel.yaml def funcparse(loader, node): node.va
之类的CloudFormation函数一起工作!Ref
。但是,当我将它们转储出去时,这些值将被删除!Ref(或任何其他函数)将用引号括起来。CloudFormation无法识别这一点
见下例:
import sys, json, io, boto3
import ruamel.yaml
def funcparse(loader, node):
node.value = {
ruamel.yaml.ScalarNode: loader.construct_scalar,
ruamel.yaml.SequenceNode: loader.construct_sequence,
ruamel.yaml.MappingNode: loader.construct_mapping,
}[type(node)](node)
node.tag = node.tag.replace(u'!Ref', 'Ref').replace(u'!', u'Fn::')
return dict([ (node.tag, node.value) ])
funcnames = [ 'Ref', 'Base64', 'FindInMap', 'GetAtt', 'GetAZs', 'ImportValue',
'Join', 'Select', 'Split', 'Split', 'Sub', 'And', 'Equals', 'If',
'Not', 'Or' ]
for func in funcnames:
ruamel.yaml.SafeLoader.add_constructor(u'!' + func, funcparse)
txt = open("/space/tmp/a.template","r")
base = ruamel.yaml.safe_load(txt)
base["foo"] = {
"name": "abc",
"Resources": {
"RouteTableId" : "!Ref aaa",
"VpcPeeringConnectionId" : "!Ref bbb",
"yourname": "dfw"
}
}
ruamel.yaml.safe_dump(
base,
sys.stdout,
default_flow_style=False
)
输入文件如下所示:
foo:
bar: !Ref barr
aa: !Ref bb
foo:
Resources:
RouteTableId: '!Ref aaa'
VpcPeeringConnectionId: '!Ref bbb'
yourname: dfw
name: abc
输出如下:
foo:
bar: !Ref barr
aa: !Ref bb
foo:
Resources:
RouteTableId: '!Ref aaa'
VpcPeeringConnectionId: '!Ref bbb'
yourname: dfw
name: abc
注意“!Ref VpcRouteTable'由单引号包装。这不会被云的形成所识别。是否有一种方法可以配置转储程序,以便输出如下所示:
foo:
Resources:
RouteTableId: !Ref aaa
VpcPeeringConnectionId: !Ref bbb
yourname: dfw
name: abc
我尝试过的其他事情:
- pyyaml库,工作原理相同
- 使用Ref::而不是!参考,工作 同样的
dict
,也没有任何特定的代码来表示这样一个映射以“获取标记”
当您尝试使用标记“创建”标量时,您只需创建一个以感叹号开头的字符串,并且需要将其引用以区分它与标记的节点
混淆这一切的是,您的示例通过分配给base[“foo”]
来覆盖加载的数据,因此您可以从安全加载中派生出的唯一东西,以及在此之前的所有代码,是它不会引发异常。也就是说,如果省略以base[“foo”]={
开头的行,您的输出将如下所示:
foo:
aa:
Ref: bb
bar:
Ref: barr
在这种情况下,Ref:bb
无法与正常转储dict区分开来。如果你想探索这条路线,那么你应该创建一个子类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
,表示这是一个(输入)文件指针)
在funcnames
中还有两次'Split'
条目,这是多余的
通过使用与任何标记匹配的多构造函数
,并具有三种基本类型来覆盖标量、映射和序列,可以实现更通用的解决方案
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
)的子类,并提供适当的\u init\u
(idem表示GenericSequence
/列表
)
当任务更改为更接近您的任务时:
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
这正是您想要的输出。除了上述Anthon的详细回答之外,对于CloudFormation模板方面的具体问题,我发现了另一个非常快速和甜蜜的解决方法
仍在使用构造函数片段加载YAML
def funcparse(loader, node):
node.value = {
ruamel.yaml.ScalarNode: loader.construct_scalar,
ruamel.yaml.SequenceNode: loader.construct_sequence,
ruamel.yaml.MappingNode: loader.construct_mapping,
}[type(node)](node)
node.tag = node.tag.replace(u'!Ref', 'Ref').replace(u'!', u'Fn::')
return dict([ (node.tag, node.value) ])
funcnames = [ 'Ref', 'Base64', 'FindInMap', 'GetAtt', 'GetAZs', 'ImportValue',
'Join', 'Select', 'Split', 'Split', 'Sub', 'And', 'Equals', 'If',
'Not', 'Or' ]
for func in funcnames:
ruamel.yaml.SafeLoader.add_constructor(u'!' + func, funcparse)
当我们操纵数据时,而不是
base["foo"] = {
"name": "abc",
"Resources": {
"RouteTableId" : "!Ref aaa",
"VpcPeeringConnectionId" : "!Ref bbb",
"yourname": "dfw"
}
}
这将用引号括起值!Ref aaa
,我们只需执行以下操作:
base["foo"] = {
"name": "abc",
"Resources": {
"RouteTableId" : {
"Ref" : "aaa"
},
"VpcPeeringConnectionId" : {
"Ref" : "bbb
},
"yourname": "dfw"
}
}
类似地,对于CloudFormation中的其他函数,例如!GetAtt,我们应该使用它们的长格式Fn::GetAtt
,并将它们用作JSON对象的键。这个问题很容易解决