다른 곳에 YAML 파일을 어떻게 포함시킬 수 있습니까?


288

따라서 "A"와 "B"라는 두 개의 YAML 파일이 있으며 A의 내용을 B와 같은 기존 데이터 구조에 연결하거나 값과 같은 요소의 자식으로 삽입하려고합니다. 특정 해시 키의 경우.

이것이 가능합니까? 어떻게? 그렇지 않다면 규범 적 참조를 가리키는 포인터가 있습니까?



1
나는 최근에 정확하게 이것을 수행하는 Python 용 HiYaPyCo 에 부딪쳤다 . 다른 YAML 파일을 함께 병합 할 수 있습니다. Is는 알만한 가치가있는 매우 훌륭한 Python 모듈입니다.
nowox

답변:


326

아니요, YAML에는 "import"또는 "include"문이 포함되어 있지 않습니다.


8
! include <filename> 핸들러를 작성할 수 있습니다.
clarkevans

5
@clarkevans는 확신하지만, 그 구조는 YAML 언어의 "외부"일 것입니다.
jameshfisher

2
이제 가능합니다. 아래에 답변을 추가했습니다 ... 도움이되기를 바랍니다.
daveaspinall

1
Rails를 사용하는 경우 <% = 'fdsa fdsa'%> ERB 구문을 삽입하면 작동합니다
gleenn

9
이 답변은 "아니요, 표준 YAML에는이 기능이 포함되어 있지 않습니다. 그럼에도 불구하고 많은 구현이 그렇게하기 위해 약간의 확장을 제공합니다."라고 대답해야한다고 생각합니다.
Franklin Yu

113

귀하의 질문은 Python 솔루션을 요구하지 않지만 PyYAML 사용하는 질문이 있습니다.

PyYAML을 사용하면 커스텀 생성자 (예 :)를 !includeYAML 로더 에 연결할 수 있습니다 . 이 솔루션이 상대 및 절대 파일 참조를 지원하도록 설정할 수있는 루트 디렉토리를 포함 시켰습니다.

클래스 기반 솔루션

다음은 원래 응답의 전역 루트 변수를 피하는 클래스 기반 솔루션입니다.

메타 클래스를 사용하여 사용자 정의 생성자를 등록하는 유사하고 강력한 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) : "" "다른 YAML 파일 포함" "" "filename = loader.construct_scalar (node) data = yaml.load (open (filename))`
Aliaksei Ramanau

루트 전역은 상대 디렉토리가 깊이에 관계없이 작업을 수행 할 수 있도록합니다. 예를 들어 다른 디렉토리에 포함 된 파일이 해당 디렉토리에 상대적인 파일을 포함하는 경우. 절대 포함도 작동해야합니다. 전역 변수없이 사용자 정의 yaml.Loader 클래스를 사용하여 더 깔끔한 방법이있을 수 있습니다.
Josh Bode

2
foo.yaml : a: bla bar.yaml :`! include foo.yaml b : blubb` 결과는 다음과 같습니다.`{ 'a': bla, 'b': blubb}
Martin

3
이것은 정답입니다. 또한 보안 nitpick의 경우 특수하게 조작 된 yaml이 서비스를 소유하지 않도록 yaml.load 대신 yaml.safeload를 사용해야합니다.
danielpops

1
@JoshBode 이것은 당신을 위해 작동해야합니다 : gist.github.com/danielpops/5a0726f2fb6288da749c4cd604276be8
다니엘 팝

32

Symfony의 YAML 버전을 사용하는 경우 다음 과 같이 가능합니다.

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

34
이것은 Symfony가 YAML 자체의 일부가 아니라 YAML을 해석하는 방식에 따라 다릅니다.
jameshfisher

9
그렇기 때문에 Symfony 문서에 대한 링크를 게시했습니다. 질문은 "이것이 전혀 가능합니까? 어떻게?"이라고 묻습니다. 공감할 이유가 없습니다.
daveaspinall

4
나는 당신을 downvote하지 않았다; 나는 이것이 Symfony YAML에만 해당된다는 것을 지적하고 있습니다.
jameshfisher

9
"YAML의 Symfony 버전"은 없습니다. 이것은 YAML의 일부가 아닌 추가 항목이있는 공급 업체별 YAML 호환 라이브러리입니다.
dreftymac 1

3
"클래스 기반"답변이 상향 투표 된 경우이 답변을 하향 투표 할 이유가 없습니다.
Mikhail

13

내가 아는 한 포함은 YAML에서 직접 지원되지 않지만 메커니즘을 직접 제공해야하지만 일반적으로 수행하기 쉽습니다.

파이썬 앱에서 YAML을 구성 언어로 사용 했으며이 경우 종종 다음과 같은 규칙을 정의합니다.

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

그런 다음 내 (파이썬) 코드에서 수행합니다.

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

유일한 단점은 include의 변수가 항상 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.5경우 YAML 태그의 recursive인수 가 인 경우 패턴 은 모든 파일 및 0 개 이상의 디렉토리 및 하위 디렉토리와 일치합니다.!include true“**”
  • “**”큰 디렉토리 트리에서 패턴을 사용하면 재귀 검색으로 인해 많은 시간이 소요될 수 있습니다.

recursive인수 를 가능하게하기 위해 !include태그를 Mapping또는 Sequence모드로 작성 합니다 :

  • Sequence모드의 인수 :
!include [tests/data/include.d/**/*.yaml, true]
  • Mapping모드의 인수 :
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

이것은 실제로 질문에 대답하지 않습니다. 표준화 된 YAML 형식을 사용하는 솔루션이 아닌 Python 솔루션과 관련이 있습니다.
oligofren 2016 년

@oligofren 커스텀 태그 핸들러는 YAML의 기능으로, 파서는 YAML을 확장하여 유형을 지정하고 이와 같은 커스텀 동작을 구현할 수 있습니다. YAML 사양 자체는 파일 포함이 모든 다른 OS 경로 사양, 파일 시스템 등에서 작동하는 방식을 규정하기까지는 오랜 시간이 걸릴 것입니다.
Anton Strogonoff

@AntonStrogonoff 내 관심을 가져 주셔서 감사합니다. RFC에서 그런 곳을 알려 주시겠습니까? "custom"이라는 단어는 언급되어 있지 않습니다. 참조 yaml.org/spec/1.2/spec.html
oligofren

1
@oligofren 천만에요. “응용 프로그램 별” 태그를 찾으십시오 .
Anton Strogonoff

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클래스를 사용하므로 솔루션이 둘 이상의 수준에서 작동하지 않습니다 .
Josh Bode

나는 키워드 제거해야 root의를 kwargs설정 한 후 self.root문자열 작업을 얻을 수 있습니다. if-else 블록을 super호출 위로 옮겼습니다 . 다른 사람이 내 결과를 확인하거나 문자열 및 root매개 변수 와 함께 클래스를 사용하는 방법을 보여줄 수 있습니다 .
울탄

1
불행하게도,이 같은 언급하지 작업을 수행합니다```포함 : & INCLUDED inner.yaml 병합을 포함한다 : << : *```포함!
안토니

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_3.yaml이 포함 된 config_2.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'

예를 들어 도커 작성 구성의 경우 :

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태그를 처리 할 수있는 단일 클래스 Java입니다 . 파일은 classpath (Maven resources 디렉토리)에서로드됩니다.

/**
 * 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 파일을 하나로 가져올 수 있습니다.

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.