如何在另一个文件中包含YAML文件?

kch*_*kch 255 yaml transclusion

所以我有两个YAML文件,"A"和"B",我想要将A的内容插入到B中,或者拼接到现有的数据结构中,像数组一样,或者作为元素的子元素,比如值对于某个哈希键.

这有可能吗?怎么样?如果没有,任何指向规范参考的指针?

jam*_*her 290

不,YAML不包含任何类型的"导入"或"包含"声明.

  • 我认为该答案应改为:“不,标准YAML不包含此功能。尽管如此,许多实现都对此提供了一些扩展。” (9认同)
  • 您可以创建一个!include <filename>处理程序. (8认同)
  • @clarkevans肯定,但那个结构将在YAML语言"之外". (4认同)
  • 这现在是可能的。我在下面添加了一个答案......希望它有所帮助。 (4认同)
  • 如果您使用 Rails,则可以插入 &lt;%= 'fdsa fdsa' %&gt; ERB 语法,它将起作用 (2认同)

Jos*_*ode 102

您的问题不是要求Python解决方案,而是使用PyYAML.

PyYAML允许您将自定义构造函数(例如!include)附加到YAML加载程序.我已经包含了一个可以设置的根目录,以便此解决方案支持相对和绝对文件引用.

基于类的解决方案

这是一个基于类的解决方案,它避免了我原始响应的全局根变量.

请参阅此要点,了解使用元类注册自定义构造函数的类似,更强大的Python 3解决方案.

import yaml
import os

class Loader(yaml.SafeLoader):

    def __init__(self, stream):

        self._root = os.path.split(stream.name)[0]

        super(Loader, self).__init__(stream)

    def include(self, node):

        filename = os.path.join(self._root, self.construct_scalar(node))

        with open(filename, 'r') as f:
            return yaml.load(f, Loader)

Loader.add_constructor('!include', Loader.include)
Run Code Online (Sandbox Code Playgroud)

一个例子:

foo.yaml

a: 1
b:
    - 1.43
    - 543.55
c: !include bar.yaml
Run Code Online (Sandbox Code Playgroud)

bar.yaml

- 3.6
- [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

现在可以使用以下方式加载文件:

>>> with open('foo.yaml', 'r') as f:
>>>    data = yaml.load(f, Loader)
>>> data
{'a': 1, 'b': [1.43, 543.55], 'c': [3.6, [1, 2, 3]]}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案.此外,安全挑剔,您应该使用yaml.safeload而不是yaml.load,以避免特制的yaml拥有您的服务. (3认同)
  • 是否也可以这样:foo.yaml:`a:bla` bar.yaml:`!include foo.yaml b:blubb`结果将是:`{'a':bla,'b ':blubb} (2认同)

dav*_*all 32

如果你正在使用Symfony的YAML版本,这是可能的,如下所示:

imports:
    - { resource: sub-directory/file.yml }
    - { resource: sub-directory/another-file.yml }
Run Code Online (Sandbox Code Playgroud)

  • 这特定于Symfony如何解释YAML,而不是YAML本身的一部分. (30认同)
  • 是的,这就是我发布Symfony docs链接的原因.问题是"这有可能吗?怎么样?"......就是这样.看到没有理由进行downvote. (7认同)
  • 没有"Symfony版本的YAML"......这只是一个特定于供应商的YAML兼容库,它有额外的东西,不属于YAML. (7认同)
  • 我没有向你投票; 我只是指出这是针对Symfony YAML的. (4认同)
  • 如果"基于阶级"的答案被推翻,没有理由拒绝这个答案. (2认同)

小智 11

据我所知,YAML不直接支持包含,但您必须自己提供一种机制,这通常很容易实现.

我在我的python应用程序中使用YAML作为配置语言,在这种情况下通常定义这样的约定:

>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
Run Code Online (Sandbox Code Playgroud)

然后在我的(python)代码中我做:

import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
   cfg.update(yaml.load(open(inc)))
Run Code Online (Sandbox Code Playgroud)

唯一的缺点是包含中的变量将始终覆盖main中的变量,并且无法通过更改"includes:"语句出现在main.yml文件中的位置来更改该优先级.

在一个稍微不同的点上,YAML不支持包含,因为它的设计并不像基于文件的标记那样完全.如果你在回答一个AJAX请求时得到了什么意思?

  • 这仅在yaml文件不包含嵌套配置时有效。 (2认同)

Max*_*y-B 7

扩展@Jos​​h_Bode的答案,这是我自己的PyYAML解决方案,它的优势在于它是一个自包含的子类yaml.Loader.它不依赖于任何模块级全局变量,也不依赖于修改yaml模块的全局状态.

import yaml, os

class IncludeLoader(yaml.Loader):                                                 
    """                                                                           
    yaml.Loader subclass handles "!include path/to/foo.yml" directives in config  
    files.  When constructed with a file object, the root path for includes       
    defaults to the directory containing the file, otherwise to the current       
    working directory. In either case, the root path can be overridden by the     
    `root` keyword argument.                                                      

    When an included file F contain its own !include directive, the path is       
    relative to F's location.                                                     

    Example:                                                                      
        YAML file /home/frodo/one-ring.yml:                                       
            ---                                                                   
            Name: The One Ring                                                    
            Specials:                                                             
                - resize-to-wearer                                                
            Effects: 
                - !include path/to/invisibility.yml                            

        YAML file /home/frodo/path/to/invisibility.yml:                           
            ---                                                                   
            Name: invisibility                                                    
            Message: Suddenly you disappear!                                      

        Loading:                                                                  
            data = IncludeLoader(open('/home/frodo/one-ring.yml', 'r')).get_data()

        Result:                                                                   
            {'Effects': [{'Message': 'Suddenly you disappear!', 'Name':            
                'invisibility'}], 'Name': 'The One Ring', 'Specials':              
                ['resize-to-wearer']}                                             
    """                                                                           
    def __init__(self, *args, **kwargs):                                          
        super(IncludeLoader, self).__init__(*args, **kwargs)                      
        self.add_constructor('!include', self._include)                           
        if 'root' in kwargs:                                                      
            self.root = kwargs['root']                                            
        elif isinstance(self.stream, file):                                       
            self.root = os.path.dirname(self.stream.name)                         
        else:                                                                     
            self.root = os.path.curdir                                            

    def _include(self, loader, node):                                    
        oldRoot = self.root                                              
        filename = os.path.join(self.root, loader.construct_scalar(node))
        self.root = os.path.dirname(filename)                           
        data = yaml.load(open(filename, 'r'))                            
        self.root = oldRoot                                              
        return data                                                      
Run Code Online (Sandbox Code Playgroud)

  • 终于开始在我的答案中添加基于类的方法,但是你打败了我的注意力:)注意:如果在`_include`中使用`yaml.load(f,IncludeLoader)`,你可以避免必须替换root .此外,除非您这样做,否则解决方案将不会超过一个级别,因为包含的数据使用常规的`yaml.Loader`类. (2认同)

lbo*_*vet 7

使用Yglu,您可以像这样导入其他文件:

亚米尔

foo: !? $import('B.yaml')
Run Code Online (Sandbox Code Playgroud)

B.yaml

bar: Hello
Run Code Online (Sandbox Code Playgroud)
$ yglu A.yaml
foo:
  bar: Hello
Run Code Online (Sandbox Code Playgroud)

$import函数一样,您也可以传递表达式作为参数:

  dep: !- b
  foo: !? $import($_.dep.toUpper() + '.yaml')
Run Code Online (Sandbox Code Playgroud)

这将给出与上面相同的输出。

免责声明:我是 Yglu 的作者。


xql*_*ang 6

对于Python用户,您可以尝试pyyaml-include

安装

pip install pyyaml-include
Run Code Online (Sandbox Code Playgroud)

用法

import yaml
from yamlinclude import YamlIncludeConstructor

YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')

with open('0.yaml') as f:
    data = yaml.load(f, Loader=yaml.FullLoader)

print(data)
Run Code Online (Sandbox Code Playgroud)

考虑我们有这样的YAML文件:

??? 0.yaml
??? include.d
    ??? 1.yaml
    ??? 2.yaml
Run Code Online (Sandbox Code Playgroud)
  • 1.yaml 的内容:
name: "1"
Run Code Online (Sandbox Code Playgroud)
  • 2.yaml 的内容:
name: "2"
Run Code Online (Sandbox Code Playgroud)

按名称包含文件

  • 在顶层:

    如果0.yaml是:

!include include.d/1.yaml
Run Code Online (Sandbox Code Playgroud)

我们会得到:

{"name": "1"}
Run Code Online (Sandbox Code Playgroud)
  • 在映射中:

    如果0.yaml是:

file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
Run Code Online (Sandbox Code Playgroud)

我们会得到:

  file1:
    name: "1"
  file2:
    name: "2"
Run Code Online (Sandbox Code Playgroud)
  • 按顺序:

    如果0.yaml是:

files:
  - !include include.d/1.yaml
  - !include include.d/2.yaml
Run Code Online (Sandbox Code Playgroud)

我们会得到:

files:
  - name: "1"
  - name: "2"
Run Code Online (Sandbox Code Playgroud)

注意事项

文件名可以是绝对名称(如/usr/conf/1.5/Make.yml)或相对名称(如../../cfg/img.yml)。

通过通配符包含文件

文件名可以包含shell样式的通配符。从通配符找到的文件中加载的数据将按顺序设置。

如果0.yaml是:

files: !include include.d/*.yaml
Run Code Online (Sandbox Code Playgroud)

我们会得到:

files:
  - name: "1"
  - name: "2"
Run Code Online (Sandbox Code Playgroud)

注意事项

  • 对于Python>=3.5,如果YAML标签的recursive参数为,则该模式将匹配任何文件以及零个或多个目录和子目录。!include true“**”
  • “**”由于递归搜索,在大型目录树中使用该模式可能会花费大量时间。

为了启用recursive参数,我们!include将以MappingSequence模式写入标记:

  • Sequence模式下的参数:
!include [tests/data/include.d/**/*.yaml, true]
Run Code Online (Sandbox Code Playgroud)
  • Mapping模式下的参数:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
Run Code Online (Sandbox Code Playgroud)

  • @oligofren 自定义标记处理程序是 YAML 的一项功能,允许解析器扩展 YAML 以指定类型并实现此类自定义行为。对于 YAML 规范本身来说,要规定文件包含如何与所有不同的操作系统路径规范、文件系统等一起工作,这将是一个漫长的过程。 (2认同)

bvd*_*vdb 5

YML 标准没有指定执行此操作的方法。而这个问题并不局限于 YML。JSON 也有同样的限制。

许多使用基于 YML 或 JSON 的配置的应用程序最终都会遇到这个问题。当这种情况发生时,他们会制定自己的约定

例如对于 swagger API 定义:

$ref: 'file.yml'
Run Code Online (Sandbox Code Playgroud)

例如对于 docker compose 配置:

services:
  app:
    extends:
      file: docker-compose.base.yml
Run Code Online (Sandbox Code Playgroud)

或者,如果您想将 yml 文件的内容拆分为多个文件,例如内容树,您可以定义自己的文件夹结构约定并使用(现有)合并脚本。

  • 这个应该更高。大多数情况下,如果您需要将 YAML 导入到另一个 YAML 中,这是因为特定框架中的某些配置文件,并且始终值得研究框架本身是否提供了一种无需重新发明轮子的方法。 (2认同)