Como posso incluir um arquivo YAML dentro de outro?
-
22-08-2019 - |
Pergunta
Então, eu tenho dois arquivos YAML, "A" e "B" e eu quero o conteúdo de A para ser inserido dentro B, seja emendado na estrutura de dados existente, como um array, ou como filho de um elemento, como o valor para uma determinada chave hash.
Isto é possível em tudo? Quão? Se não, qualquer ponteiros para uma referência normativa?
Solução
Não, YAML não inclui qualquer tipo de "importação" ou "incluir" declaração.
Outras dicas
A sua questão não pede uma solução Python, mas aqui está um usando PyYAML .
PyYAML permite anexar construtores personalizados (tais como !include
) para o carregador YAML. Eu incluí um diretório raiz que pode ser configurado de modo a que esta solução suporta referências relativas e absolutas de arquivo.
Class-Based Solution
Aqui é uma solução baseada em classes, que evita a variável raiz mundial da minha resposta original.
Veja este essência para uma mais robusta solução semelhante, Python 3 que usa um metaclass a registar o construtor personalizado.
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)
Um exemplo:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
Agora, os arquivos podem ser carregados usando:
>>> 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]]}
Se você estiver usando a versão do Symfony de YAML , isso é possível, como este:
imports:
- { resource: sub-directory/file.yml }
- { resource: sub-directory/another-file.yml }
Inclui não são suportados diretamente no YAML, tanto quanto eu sei, você terá que fornecer um mecanismo de si mesmo no entanto, esta é geralmente fácil de fazer.
Eu tenho usado YAML como uma linguagem de configuração em meus aplicativos Python, e neste caso, muitas vezes definir uma convenção como esta:
>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
Em seguida, no meu código (Python) eu faço:
import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
cfg.update(yaml.load(open(inc)))
A única desvantagem é que as variáveis ??no inclui sempre substituirá as variáveis ??no principal, e não há nenhuma maneira de mudar isso precedência alterando onde o "inclui:. Aparece instrução no arquivo main.yml
Em um ponto ligeiramente diferente, YAML não suporta inclui como não é realmente concebido como como exclusivamente como uma marca baseada em arquivo para cima. O que seria um incluem dizer, se você tem isso em resposta a um pedido de AJAX?
Expandindo resposta da @ Josh_Bode, aqui está a minha própria solução PyYAML, que tem a vantagem de ser uma subclasse independente de yaml.Loader
. Ele não depende de qualquer globals de nível de módulo, ou modificar o estado global do módulo 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
Para usuários de Python, você pode tentar PyYAML-incluem .
Instalar
pip install pyyaml-include
Uso
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)
Considere que temos tais YAML arquivos:
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
-
1.yaml
de conteúdo:
name: "1"
-
2.yaml
de conteúdo:
name: "2"
Incluir arquivos por nome
-
No nível superior:
Se
0.yaml
foi:
!include include.d/1.yaml
Nós vamos chegar:
{"name": "1"}
-
No mapeamento:
Se
0.yaml
foi:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
Nós vamos chegar:
file1:
name: "1"
file2:
name: "2"
-
Na sequência:
Se
0.yaml
foi:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
Nós vamos chegar:
files:
- name: "1"
- name: "2"
? Nota ??strong>:
O nome do arquivo pode ser absoluto (como
/usr/conf/1.5/Make.yml
) ou relativa (como../../cfg/img.yml
).
Incluir arquivos por wildcards
O nome do arquivo pode conter wildcards de estilo concha. Os dados carregados a partir do ficheiro (s) encontrada por caracteres especiais serão estabelecidos numa sequência.
Se 0.yaml
foi:
files: !include include.d/*.yaml
Nós vamos chegar:
files:
- name: "1"
- name: "2"
? Nota ??strong>:
- Para
Python>=3.5
, se o argumentorecursive
de!include
YAML tag étrue
, o“**”
padrão irá corresponder a todos os arquivos e Zero ou mais diretórios e subdiretórios.- Usando o padrão
“**”
em grandes árvores de diretório pode consumir uma quantidade excessiva de tempo por causa de pesquisa recursiva.
A fim de permitir argumento recursive
, vamos escrever a tag !include
no modo Mapping
ou Sequence
:
- Os argumentos em modo
Sequence
:
!include [tests/data/include.d/**/*.yaml, true]
- Os argumentos em modo
Mapping
:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
Infelizmente YAML não fornece isso em seu padrão.
Mas se você estiver usando Ruby, há uma jóia fornecer a funcionalidade que você está pedindo, alargando a biblioteca Ruby YAML: https://github.com/entwanderer/yaml_extend
Eu acho que a solução usada por @ maxy-B parece ótimo. No entanto, ele não teve sucesso para mim, com inclusões aninhados. Por exemplo, se config_1.yaml inclui config_2.yaml, que inclui config_3.yaml havia um problema com o carregador. No entanto, se você simplesmente apontar a nova classe carregador para si sobre a carga, ele funciona! Especificamente, se substituirmos a função _Include velho com a versão ligeiramente modificada:
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
Após a reflexão Concordo com os outros comentários, que o carregamento aninhada não é apropriado para yaml em geral, como o fluxo de entrada não pode ser um arquivo, mas é muito útil!
Talvez isso pode inspirá-lo, tente alinhar a convenções 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
Padrão YAML 1.2 não inclui nativamente este recurso. No entanto muitas implementações fornece alguma extensão para fazê-lo.
Eu apresento uma maneira de alcançá-la com Java e snakeyaml:1.24
(biblioteca Java para emitir arquivos de análise / YAML), que permite a criação de um costume YAML tag para alcançar o seguinte objetivo (você vai ver que eu estou usando-o para suites de teste de carga definidos em vários arquivos YAML e que fez funcionar como uma lista de inclui um nó de destino test:
):
# ... yaml prev stuff
tests: !include
- '1.hello-test-suite.yaml'
- '3.foo-test-suite.yaml'
- '2.bar-test-suite.yaml'
# ... more yaml document
Aqui está o Java de uma classe que permite o processamento da tag !include
. Os arquivos são carregados a partir de classpath (diretório de recursos do 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));
}
}
}
}
}
Com Symfony , sua manipulação de yaml, indiretamente permitem arquivos YAML ninho. O truque é fazer uso da opção parameters
. por exemplo:
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%"
O resultado será o mesmo que:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
Provavelmente não foi suportada quando pergunta foi feita, mas você pode importar outro arquivo YAML em um:
imports: [/your_location_to_yaml_file/Util.area.yaml]
Embora eu não tenho qualquer referência online, mas isso funciona para mim.