따라서 "A"와 "B"라는 두 개의 YAML 파일이 있으며 A의 내용을 B와 같은 기존 데이터 구조에 연결하거나 값과 같은 요소의 자식으로 삽입하려고합니다. 특정 해시 키의 경우.
이것이 가능합니까? 어떻게? 그렇지 않다면 규범 적 참조를 가리키는 포인터가 있습니까?
따라서 "A"와 "B"라는 두 개의 YAML 파일이 있으며 A의 내용을 B와 같은 기존 데이터 구조에 연결하거나 값과 같은 요소의 자식으로 삽입하려고합니다. 특정 해시 키의 경우.
이것이 가능합니까? 어떻게? 그렇지 않다면 규범 적 참조를 가리키는 포인터가 있습니까?
답변:
아니요, YAML에는 "import"또는 "include"문이 포함되어 있지 않습니다.
귀하의 질문은 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]]}
include
함수 단순화 할 수 :`def include (loader, node) : "" "다른 YAML 파일 포함" "" "filename = loader.construct_scalar (node) data = yaml.load (open (filename))`
a: bla
bar.yaml :`! include foo.yaml b : blubb` 결과는 다음과 같습니다.`{ 'a': bla, 'b': blubb}
Symfony의 YAML 버전을 사용하는 경우 다음 과 같이 가능합니다.
imports:
- { resource: sub-directory/file.yml }
- { resource: sub-directory/another-file.yml }
내가 아는 한 포함은 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 요청에 대한 응답으로 포함하면 포함이란 무엇을 의미합니까?
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}
@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
yaml.load(f, IncludeLoader)
내에서 사용 _include
하면 루트를 교체하지 않아도됩니다. 또한이 작업을 수행하지 않으면 포함 된 데이터가 일반 yaml.Loader
클래스를 사용하므로 솔루션이 둘 이상의 수준에서 작동하지 않습니다 .
root
의를 kwargs
설정 한 후 self.root
문자열 작업을 얻을 수 있습니다. if-else 블록을 super
호출 위로 옮겼습니다 . 다른 사람이 내 결과를 확인하거나 문자열 및 root
매개 변수 와 함께 클래스를 사용하는 방법을 보여줄 수 있습니다 .
나는 당신의 참고를 위해 몇 가지 예를 만듭니다.
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
이렇게 결합하면됩니다
# 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.
불행히도 YAML은 이것을 표준으로 제공하지 않습니다.
그러나 Ruby를 사용하는 경우, Ruby YAML 라이브러리를 확장하여 원하는 기능을 제공하는 gem이 있습니다 : https://github.com/entwanderer/yaml_extend
@ 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에 적합하지 않지만 매우 유용합니다!
YML 표준 은 이를 수행하는 방법을 지정 하지 않습니다 . 그리고이 문제는 YML에만 국한되지 않습니다. JSON에도 동일한 제한이 있습니다.
YML 또는 JSON 기반 구성을 사용하는 많은 애플리케이션이 결국이 문제에 부딪칩니다. 그리고 그 일이 발생하면 그들은 그들 자신의 컨벤션을 구성합니다 합니다.
예를 들어 swagger API 정의의 경우 :
$ref: 'file.yml'
예를 들어 도커 작성 구성의 경우 :
services:
app:
extends:
file: docker-compose.base.yml
또는 컨텐츠 트리와 같이 yml 파일의 컨텐츠를 여러 파일로 분할하려는 경우 고유 한 폴더 구조 규칙을 정의하고 (기존) 병합 스크립트를 사용할 수 있습니다.
어쩌면 이것이 당신에게 영감을 줄 수 있고, jbb 규칙에 맞춰보십시오 :
https://docs.openstack.org/infra/jenkins-job-builder/definition.html#inclusion-tags
- job:
name: test-job-include-raw-1
builders:
- shell:
!include-raw: include-raw001-hello-world.sh
표준 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));
}
}
}
}
}
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"