YAMLファイルを別のファイルに含めるにはどうすればよいですか?


288

したがって、「A」と「B」の2つのYAMLファイルがあり、Aの内容をBの内部に挿入して、配列などの既存のデータ構造にスプライスするか、値のように要素の子として特定のハッシュキー。

これはまったく可能ですか?どうやって?そうでない場合、規範的な参照へのポインタ?



1
私は最近、まさにこれを行うPythonのHiYaPyCoにぶつかりました。異なるYAMLファイルをマージできます。知っておく価値のある非常に素晴らしいPythonモジュールです。
nowox

回答:


326

いいえ、YAMLには、いかなる種類の「インポート」または「インクルード」ステートメントも含まれていません。


8
!include <filename>ハンドラを作成できます。
clarkevans 2014年

5
@clarkevans確かに、その構造はYAML言語の「外側」になります。
jameshfisher 2014年

2
これが可能になりました。以下に回答を追加しました...それが役に立てば幸いです。
daveaspinall 2015

1
Railsを使用している場合は、<%= 'fdsa fdsa'%> ERB構文を挿入すると機能します
gleenn

9
この答えは、「いいえ、標準のYAMLにはこの関数が含まれていません。それでも多くの実装には、そうするための拡張機能がいくつか用意されています」と言い換えるべきだと思います。
フランクリンユー

112

あなたの質問は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)

例:

foo.yaml

a: 1
b:
    - 1.43
    - 543.55
c: !include bar.yaml

bar.yaml

- 3.6
- [1, 2, 3]

これで、次のコマンドを使用してファイルをロードできます。

>>> 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]]}

これは魅力的な機能です。しかし、root / old_rootを使用したこれらすべての操作の目的は何ですか?include関数のコードを簡略化できると思います: `def include(loader、node):" "" Include another YAML file。 "" "filename = loader.construct_scalar(node)data = yaml.load(open(filename))`
アリアクセイラマナウ

ルートグローバルが存在するため、たとえば、別のディレクトリにあるインクルードファイルにそのディレクトリに関連するファイルが含まれる場合など、相対インクルードは任意の深さで作業を行います。絶対インクルードも機能するはずです。おそらくカスタムyaml.Loaderクラスを使用して、グローバル変数なしでこれを行うためのよりクリーンな方法があるでしょう。
Josh Bode

2
次のようなものを持つこともできますか:foo.yaml: a: bla bar.yaml: `!include foo.yaml b:blubb`結果は次のようになります:` {'a':bla、 'b':blubb}
マーティン

3
これは受け入れられる答えになるはずです。また、セキュリティのヒントとして、特別に細工されたyamlがサービスを所有しないように、yaml.loadではなくyaml.safeloadを使用する必要があります。
danielpops

1
@JoshBodeこれはあなたのために働くはずです:gist.github.com/danielpops/5a0726f2fb6288da749c4cd604276be8
danielpops

32

SymfonyのバージョンのYAMLを使用している場合、これは次のように可能です。

imports:
    - { resource: sub-directory/file.yml }
    - { resource: sub-directory/another-file.yml }

34
これは、YAML自体の一部ではなく、SymfonyがYAMLを解釈する方法に固有です。
jameshfisher 2015

9
そうです、私がSymfonyのドキュメントへのリンクを投稿したのはそのためです。「これはまったく可能でしょうか?どうやって?」という質問です...これが方法です。反対投票の理由はありません。
daveaspinall

4
私はあなたに反対票を投じませんでした。これはSymfony YAMLに固有のものであることを指摘しています。
jameshfisher 2015

9
「SymfonyバージョンのYAML」はありません...これは単に、YAMLの一部ではない追加の要素を持つベンダー固有のYAML互換ライブラリです。
dreftymac 2017

3
「クラスベース」の回答が賛成されている場合、この回答を反対する理由はありません。
ミハイル

13

インクルードは、私が知る限り、YAMLで直接サポートされていません。メカニズムを自分で提供する必要がありますが、これは一般的に簡単です。

私のPythonアプリでは構成言語としてYAMLを使用しており、この場合、次のような規則を定義することがよくあります。

>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]

次に、私の(python)コードで私は行います:

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

唯一の欠点は、includes内の変数が常にmain内の変数をオーバーライドすることです。 "includes:ステートメントがmain.ymlファイル内のどこにあるかを変更することによって、その優先順位を変更する方法はありません。

少し異なる点で、YAMLはインクルードをサポートしていません。実際には、ファイルベースのマークアップほど排他的に設計されていません。AJAXリクエストへの応答でインクルードした場合、インクルードとはどういう意味ですか?


3
これは、yamlファイルにネストされた構成が含まれていない場合にのみ機能します。
Freedom

10

Pythonユーザーの場合は、pyyaml-includeを試すことができます

インストール

pip install pyyaml-include

使用法

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)

このようなYAMLファイルがあるとしましょう。

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • 1.yaml のコンテンツ:
name: "1"
  • 2.yaml のコンテンツ:
name: "2"

名前でファイルを含める

  • トップレベル:

    だった場合0.yaml

!include include.d/1.yaml

取得します:

{"name": "1"}
  • マッピング:

    だった場合0.yaml

file1: !include include.d/1.yaml
file2: !include include.d/2.yaml

取得します:

  file1:
    name: "1"
  file2:
    name: "2"
  • 順番通りに:

    だった場合0.yaml

files:
  - !include include.d/1.yaml
  - !include include.d/2.yaml

取得します:

files:
  - name: "1"
  - name: "2"

注意

ファイル名は、絶対(など/usr/conf/1.5/Make.yml)または相対(など../../cfg/img.yml)のいずれかです。

ワイルドカードでファイルを含める

ファイル名には、シェルスタイルのワイルドカードを含めることができます。ワイルドカードで見つかったファイルから読み込まれたデータは、順番に設定されます。

だった場合0.yaml

files: !include include.d/*.yaml

取得します:

files:
  - name: "1"
  - name: "2"

注意

  • の場合Python>=3.5YAMLタグのrecursive引数がの場合、パターンはすべてのファイルと0個以上のディレクトリおよびサブディレクトリに一致します。!include true“**”
  • “**”大規模なディレクトリツリーでパターンを使用すると、再帰的な検索が行われるため、非常に時間がかかる場合があります。

recursive引数を有効にするために!includeMappingまたはSequenceモードでタグを記述します。

  • Sequenceモードの引数:
!include [tests/data/include.d/**/*.yaml, true]
  • Mappingモードの引数:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

これは実際には質問の答えにはなりません。これはPythonソリューションに関するものであり、標準化されたYAML形式を使用するものではありません。
オリゴフレン

@oligofrenカスタムタグハンドラーはYAMLの機能であり、パーサーはYAMLを拡張してタイプを指定し、これらのようなカスタム動作を実装できます。YAML仕様自体は、ファイルのインクルードは、などすべての異なるOSのパスの仕様、ファイルシステムと連携する方法として、これまで規定するように行くことは、長いストレッチだろう
アントンStrogonoff

@AntonStrogonoffよろしくお願いします。RFCのそのような場所を教えていただけますか?「カスタム」という言葉については触れられていない。参照yaml.org/spec/1.2/spec.html
オリゴフレン

1
@oligofrenどういたしまして。「アプリケーション固有」のタグを探します
アントンストロゴノフ

8

@Josh_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                                                      

2
最後に、クラスベースのアプローチを私の答えに追加することに成功しましたが、パンチに私を打ち負かしました:)注:yaml.load(f, IncludeLoader)内で使用する_include場合は、ルートを置き換える必要を回避できます。また、これを行わない限り、含まれるデータが通常のyaml.Loaderクラスを使用するため、ソリューションは1レベルより深く機能しません。
Josh Bode

文字列で機能するようにするにrootkwargs、設定後にキーワードを削除するself.root必要がありました。if-elseブロックをsuper呼び出しの上に移動しました。多分誰かが私の発見を確認したり、文字列とrootパラメーターでクラスを使用する方法を私に示すことができます。
ウォルタン

1
残念ながら、これは `` `のような参照では機能しません:&INCLUDED!include inner.yaml merge:<<:* INCLUDED` ``
antony

2

参考のためにいくつか例を示します。

import yaml

main_yaml = """
Package:
 - !include _shape_yaml    
 - !include _path_yaml
"""

_shape_yaml = """
# Define
Rectangle: &id_Rectangle
    name: Rectangle
    width: &Rectangle_width 20
    height: &Rectangle_height 10
    area: !product [*Rectangle_width, *Rectangle_height]

Circle: &id_Circle
    name: Circle
    radius: &Circle_radius 5
    area: !product [*Circle_radius, *Circle_radius, pi]

# Setting
Shape:
    property: *id_Rectangle
    color: red
"""

_path_yaml = """
# Define
Root: &BASE /path/src/

Paths: 
    a: &id_path_a !join [*BASE, a]
    b: &id_path_b !join [*BASE, b]

# Setting
Path:
    input_file: *id_path_a
"""


# define custom tag handler
def yaml_import(loader, node):
    other_yaml_file = loader.construct_scalar(node)
    return yaml.load(eval(other_yaml_file), Loader=yaml.SafeLoader)


def yaml_product(loader, node):
    import math
    list_data = loader.construct_sequence(node)
    result = 1
    pi = math.pi
    for val in list_data:
        result *= eval(val) if isinstance(val, str) else val
    return result


def yaml_join(loader, node):
    seq = loader.construct_sequence(node)
    return ''.join([str(i) for i in seq])


def yaml_ref(loader, node):
    ref = loader.construct_sequence(node)
    return ref[0]


def yaml_dict_ref(loader: yaml.loader.SafeLoader, node):
    dict_data, key, const_value = loader.construct_sequence(node)
    return dict_data[key] + str(const_value)


def main():
    # register the tag handler
    yaml.SafeLoader.add_constructor(tag='!include', constructor=yaml_import)
    yaml.SafeLoader.add_constructor(tag='!product', constructor=yaml_product)
    yaml.SafeLoader.add_constructor(tag='!join', constructor=yaml_join)
    yaml.SafeLoader.add_constructor(tag='!ref', constructor=yaml_ref)
    yaml.SafeLoader.add_constructor(tag='!dict_ref', constructor=yaml_dict_ref)

    config = yaml.load(main_yaml, Loader=yaml.SafeLoader)

    pk_shape, pk_path = config['Package']
    pk_shape, pk_path = pk_shape['Shape'], pk_path['Path']
    print(f"shape name: {pk_shape['property']['name']}")
    print(f"shape area: {pk_shape['property']['area']}")
    print(f"shape color: {pk_shape['color']}")

    print(f"input file: {pk_path['input_file']}")


if __name__ == '__main__':
    main()

出力

shape name: Rectangle
shape area: 200
shape color: red
input file: /path/src/a

アップデート2

このように組み合わせることができます

# xxx.yaml
CREATE_FONT_PICTURE:
  PROJECTS:
    SUNG: &id_SUNG
      name: SUNG
      work_dir: SUNG
      output_dir: temp
      font_pixel: 24


  DEFINE: &id_define !ref [*id_SUNG]  # you can use config['CREATE_FONT_PICTURE']['DEFINE'][name, work_dir, ... font_pixel]
  AUTO_INIT:
    basename_suffix: !dict_ref [*id_define, name, !product [5, 3, 2]]  # SUNG30

# ↓ This is not correct.
# basename_suffix: !dict_ref [*id_define, name, !product [5, 3, 2]]  # It will build by Deep-level. id_define is Deep-level: 2. So you must put it after 2. otherwise, it can't refer to the correct value.


1

@ maxy-Bで使用されているソリューションは素晴らしいと思います。ただし、ネストされたインクルージョンでは、成功しませんでした。たとえば、config_1.yamlにconfig_2.yamlが含まれている場合、config_2.yamlにはconfig_3.yamlが含まれ、ローダーに問題がありました。ただし、ロード時に新しいローダークラスをそれ自体にポイントするだけで機能します。具体的には、古い_include関数をわずかに変更したバージョンに置き換えた場合:

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'), loader = IncludeLoader)                            
     self.root = oldRoot                                              
     return data

反映すると、他のコメントに同意します。ネストされた読み込みは、入力ストリームがファイルではない可能性があるため、一般的にyamlには適していませんが、非常に便利です。


1

YML標準、これを行う方法を指定していません。そして、この問題はYMLに限定されません。JSONにも同じ制限があります。

YMLまたはJSONベースの構成を使用する多くのアプリケーションは、最終的にこの問題に遭遇します。そしてそれが起こるとき、彼らは彼ら自身の慣習を作り上げます。

たとえば、swagger API定義の場合:

$ref: 'file.yml'

例:docker compose構成:

services:
  app:
    extends:
      file: docker-compose.base.yml

あるいは、コンテンツのツリーのように、ymlファイルのコンテンツを複数のファイルに分割したい場合は、独自のフォルダー構造規則を定義し、(既存の)マージスクリプトを使用できます。



0

標準のYAML 1.2には、この機能は本来含まれていません。それにもかかわらず、多くの実装はそうするためにいくつかの拡張を提供します。

私はそれをJavaで実現する方法を提示し、snakeyaml:1.24(YAMLファイルを解析/放出するJavaライブラリ)カスタムYAMLタグを作成して次の目標を達成できるようにします(いくつかのYAMLファイルで定義されたテストスイートをロードするためにそれを使用していることがわかります)そして私はそれをターゲットtest:ノードのインクルードのリストとして機能させました):

# ... yaml prev stuff

tests: !include
  - '1.hello-test-suite.yaml'
  - '3.foo-test-suite.yaml'
  - '2.bar-test-suite.yaml'

# ... more yaml document

これは、!includeタグを処理できる1クラスのJavaです。ファイルはクラスパス(Mavenリソースディレクトリ)から読み込まれます。

/**
 * Custom YAML loader. It adds support to the custom !include tag which allows splitting a YAML file across several
 * files for a better organization of YAML tests.
 */
@Slf4j   // <-- This is a Lombok annotation to auto-generate logger
public class MyYamlLoader {

    private static final Constructor CUSTOM_CONSTRUCTOR = new MyYamlConstructor();

    private MyYamlLoader() {
    }

    /**
     * Parse the only YAML document in a stream and produce the Java Map. It provides support for the custom !include
     * YAML tag to split YAML contents across several files.
     */
    public static Map<String, Object> load(InputStream inputStream) {
        return new Yaml(CUSTOM_CONSTRUCTOR)
                .load(inputStream);
    }


    /**
     * Custom SnakeYAML constructor that registers custom tags.
     */
    private static class MyYamlConstructor extends Constructor {

        private static final String TAG_INCLUDE = "!include";

        MyYamlConstructor() {
            // Register custom tags
            yamlConstructors.put(new Tag(TAG_INCLUDE), new IncludeConstruct());
        }

        /**
         * The actual include tag construct.
         */
        private static class IncludeConstruct implements Construct {

            @Override
            public Object construct(Node node) {
                List<Node> inclusions = castToSequenceNode(node);
                return parseInclusions(inclusions);
            }

            @Override
            public void construct2ndStep(Node node, Object object) {
                // do nothing
            }

            private List<Node> castToSequenceNode(Node node) {
                try {
                    return ((SequenceNode) node).getValue();

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The !import value must be a sequence node, but " +
                            "'%s' found.", node));
                }
            }

            private Object parseInclusions(List<Node> inclusions) {

                List<InputStream> inputStreams = inputStreams(inclusions);

                try (final SequenceInputStream sequencedInputStream =
                             new SequenceInputStream(Collections.enumeration(inputStreams))) {

                    return new Yaml(CUSTOM_CONSTRUCTOR)
                            .load(sequencedInputStream);

                } catch (IOException e) {
                    log.error("Error closing the stream.", e);
                    return null;
                }
            }

            private List<InputStream> inputStreams(List<Node> scalarNodes) {
                return scalarNodes.stream()
                        .map(this::inputStream)
                        .collect(toList());
            }

            private InputStream inputStream(Node scalarNode) {
                String filePath = castToScalarNode(scalarNode).getValue();
                final InputStream is = getClass().getClassLoader().getResourceAsStream(filePath);
                Assert.notNull(is, String.format("Resource file %s not found.", filePath));
                return is;
            }

            private ScalarNode castToScalarNode(Node scalarNode) {
                try {
                    return ((ScalarNode) scalarNode);

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The value must be a scalar node, but '%s' found" +
                            ".", scalarNode));
                }
            }
        }

    }

}

0

ではYglu、あなたはこのような他のファイルをインポートすることができます。

A.yaml

foo: !? $import('B.yaml')

B.yaml

bar: Hello
$ yglu A.yaml
foo:
  bar: Hello

$import関数と同様に、引数として式を渡すこともできます。

  dep: !- b
  foo: !? $import($_.dep.toUpper() + '.yaml')

これにより、上記と同じ出力が得られます。

免責事項:私はYgluの作者です。


-1

symfonyの、YAMLの取り扱いは、間接的にネストYAMLファイルにあなたをできるようになります。秘訣は、parametersオプションを利用することです。例えば:

common.yml

parameters:
    yaml_to_repeat:
        option: "value"
        foo:
            - "bar"
            - "baz"

config.yml

imports:
    - { resource: common.yml }
whatever:
    thing: "%yaml_to_repeat%"
    other_thing: "%yaml_to_repeat%"

結果は次のようになります。

whatever:
    thing:
        option: "value"
        foo:
            - "bar"
            - "baz"
    other_thing:
        option: "value"
        foo:
            - "bar"
            - "baz"

-6

質問があったときにおそらくサポートされていませんが、他のYAMLファイルを1つにインポートできます

imports: [/your_location_to_yaml_file/Util.area.yaml]

オンライン参照はありませんが、これでうまくいきます。


4
これは何も含めて何もしません。キーの値として、単一の文字列「/your_location_to_yaml_file/Util.area.yaml」で構成されるシーケンスを使用してマッピングを作成しますimports
Anthon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.