如何将PrettyDumper与字符串表示器结合生成合规PyYAML输出?
解决PyYAML缩进与多行字符串格式兼容问题
问题场景
使用PyYAML 6.0 + Python 3.9,需完成以下操作:
- 创建YAML列表
- 将该列表作为多行字符串嵌入另一个YAML对象
- 在现有文档中替换该YAML对象
- 输出符合YAML 1.2规范的文档
现有实现存在矛盾问题:
- 仅启用多行字符串格式化时,列表项缩进不符合要求,触发仓库预提交检查的自动格式调整
- 仅使用自定义缩进的
PrettyDumper时,多行字符串格式失效,变成带转义符的单行字符串
错误合并方案问题点
之前合并str_presenter与PrettyDumper时,错误地将字符串格式化逻辑注册到全局PyYAML,而非自定义Dumper实例内部,导致多行字符串规则未被正确调用。
正确实现代码
import yaml.emitter import yaml.serializer import yaml.representer import yaml.resolver class IndentingEmitter(yaml.emitter.Emitter): def increase_indent(self, flow=False, indentless=False): """确保列表项始终缩进""" return super().increase_indent( flow=False, indentless=False, ) class PrettyDumper( IndentingEmitter, yaml.serializer.Serializer, yaml.representer.Representer, yaml.resolver.Resolver, ): def __init__( self, stream, default_style=None, default_flow_style=False, canonical=None, indent=2, # 显式指定缩进为2,符合YAML 1.2规范 width=None, allow_unicode=None, line_break=None, encoding=None, explicit_start=None, explicit_end=None, version=None, tags=None, sort_keys=True, ): IndentingEmitter.__init__( self, stream, canonical=canonical, indent=indent, width=width, allow_unicode=allow_unicode, line_break=line_break, ) yaml.serializer.Serializer.__init__( self, encoding=encoding, explicit_start=explicit_start, explicit_end=explicit_end, version=version, tags=tags, ) yaml.representer.Representer.__init__( self, default_style=default_style, default_flow_style=default_flow_style, sort_keys=sort_keys, ) yaml.resolver.Resolver.__init__(self) # 在当前Dumper实例注册字符串格式化逻辑,而非全局注册 self.add_representer(str, self.str_presenter) # 兼容安全序列化场景 self.add_representer(str, self.str_presenter, yaml.representer.SafeRepresenter) def str_presenter(self, dumper, data): """配置多行字符串使用|样式格式化""" if data.count('\n') > 0: return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') return dumper.represent_scalar('tag:yaml.org,2002:str', data) # 修改业务类,使用自定义PrettyDumper class DoYamlStuff: @staticmethod def post_renderers(images): return yaml.dump([ { "op": "replace", "path": "/spec/postRenderers", "value": [ { "kustomize": { "images": images } } ] }], Dumper=PrettyDumper) @classmethod def images_patch(cls, chart, images, ecr_url): return { "target": { "kind": "HelmRelease", "name": chart, "namespace": chart }, "patch": cls.post_renderers([x.patch(ecr_url) for x in images]) } # 测试输出 if __name__ == "__main__": # 模拟Image对象 class MockImage: def __init__(self, name): self.name = name def patch(self, ecr_url): return { "name": f"{self.name}:latest", "newName": f"{ecr_url}/{self.name}", "newTag": "latest" } patches = [DoYamlStuff.images_patch("nginx", [MockImage("nginx")], "12345678910.dkr.ecr.eu-west-1.amazonaws.com")] print(yaml.dump(patches, Dumper=PrettyDumper))
正确输出示例
- patch: | - op: replace path: /spec/postRenderers value: - kustomize: images: - name: nginx:latest newName: 12345678910.dkr.ecr.eu-west-1.amazonaws.com/nginx newTag: latest target: kind: HelmRelease name: nginx namespace: nginx
关键修复点
- 缩进控制:通过
IndentingEmitter强制列表项缩进,同时显式指定indent=2,严格匹配YAML 1.2规范 - 多行字符串逻辑:将
str_presenter注册到自定义PrettyDumper实例内部,确保缩进与多行字符串规则同时生效 - 兼容安全序列化:同时将格式化逻辑注册到
SafeRepresenter,避免安全模式下格式失效
内容的提问来源于stack exchange,提问作者turbonerd
相关产品推荐
相关产品推荐

