¿Cómo puedo incluir un archivo YAML dentro de otro?
-
22-08-2019 - |
Pregunta
Así que tengo dos archivos YAML, "A" y "B" y quiero que el contenido de A a insertarse en el interior B, o bien empalmados en la estructura de los datos existentes, como una matriz, o como hijo de un elemento, al igual que el valor de una determinada clave hash.
¿Es esto posible? ¿Cómo? Si no es así, cualquier puntero a una referencia normativa?
Solución
No, YAML no incluye ningún tipo de "importación" o "incluir" declaración.
Otros consejos
Su pregunta no pide una solución Python, pero aquí es uno usando PyYAML .
PyYAML le permite adjuntar constructores personalizados (como !include
) al cargador YAML. He incluido un directorio raíz que se puede configurar para que esta solución es compatible con las referencias de archivos relativos y absolutos.
Clase-solución basada
Aquí es una solución basada en la clase, que evita la variable raíz mundial de mi respuesta original.
GIST para una más robusta solución similar, Python 3 que utiliza una metaclase para registrarse el constructor 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)
Un ejemplo:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
Ahora los archivos se pueden cargar 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]]}
Si está utilizando la versión de Symfony de YAML , esto es posible, de esta manera:
imports:
- { resource: sub-directory/file.yml }
- { resource: sub-directory/another-file.yml }
Incluye no son compatibles directamente en YAML por lo que yo sé, usted tendrá que proporcionar un mecanismo de sí mismo Sin embargo, esto es generalmente fácil de hacer.
He utilizado YAML como un lenguaje de configuración en mis aplicaciones de pitón, y en este caso suelen definir una convención de la siguiente manera:
>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
A continuación, en mi código (Python) que hago:
import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
cfg.update(yaml.load(open(inc)))
El único aspecto negativo es que las variables en los includes siempre neutralizan las variables en el principal, y no hay manera de cambiar la precedencia que cambiando donde el "incluye: declaración aparece en el archivo main.yml
.En un punto ligeramente diferente, YAML no soporta incluye ya que no es realmente diseñado como ya exclusivamente como una marca basado en archivos para arriba. Lo que sería una media incluir si lo ha hecho en respuesta a una petición AJAX?
Ampliando la respuesta de @ Josh_Bode, aquí está mi propia solución PyYAML, que tiene la ventaja de ser una subclase autónomo de yaml.Loader
. Que no depende de ningún globales a nivel de módulo, o en la modificación del estado global del 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 los usuarios de Python, puede intentar PyYAML-incluir .
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)
Tenga en cuenta que tenemos este tipo de archivos YAML :
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
- contenido
1.yaml
's:
name: "1"
- contenido
2.yaml
's:
name: "2"
Incluir archivos por nombre
-
El nivel superior:
Si
0.yaml
era:
!include include.d/1.yaml
Vamos a llegar:
{"name": "1"}
-
En mapeo:
Si
0.yaml
era:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
Vamos a llegar:
file1:
name: "1"
file2:
name: "2"
-
En secuencia:
Si
0.yaml
era:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
Vamos a llegar:
files:
- name: "1"
- name: "2"
ℹ Nota:
nombre de archivo puede ser absoluta (como
/usr/conf/1.5/Make.yml
) o relativa (como../../cfg/img.yml
).
Incluir archivos mediante comodines
Nombre de archivo puede contener comodines de tipo concha. Los datos cargados desde el archivo (s) encontrado por comodines se establecerán en una secuencia.
Si 0.yaml
era:
files: !include include.d/*.yaml
Vamos a llegar:
files:
- name: "1"
- name: "2"
ℹ Nota:
- Para
Python>=3.5
, si el argumento derecursive
!include
etiqueta YAML estrue
, la“**”
patrón coincidirá con todos los archivos y cero o más directorios y subdirectorios.- Uso del patrón de
“**”
en grandes árboles de directorios puede consumir una cantidad excesiva de tiempo debido a la búsqueda recursiva.
A fin de que el argumento recursive
, escribiremos la etiqueta !include
en modo Mapping
o Sequence
:
- Los argumentos a modo de
Sequence
:
!include [tests/data/include.d/**/*.yaml, true]
- Los argumentos a modo de
Mapping
:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
Desafortunadamente YAML no proporciona esta en su estándar.
Sin embargo, si usted está usando Ruby, hay una joya que proporciona la funcionalidad que está pidiendo al extender la biblioteca de rubí YAML: https://github.com/entwanderer/yaml_extend
Creo que la solución utilizada por @ Maxy-B se ve muy bien. Sin embargo, no tuvo éxito para mí con inclusiones anidadas. Por ejemplo, si config_1.yaml incluye config_2.yaml, que incluye config_3.yaml había un problema con el cargador. Sin embargo, si simplemente apuntar el nuevo cargador de clases a sí mismo en la carga, funciona! En concreto, si se sustituye la antigua función _Incluir con la versión ligeramente 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
Sobre la reflexión Estoy de acuerdo con los otros comentarios, que la carga anidada no es apropiado para yaml en general, como el flujo de entrada no puede ser un archivo, pero es muy útil!
Tal vez esto podría inspirar a usted, trate de alinear a las convenciones 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
Estándar YAML 1.2 no incluye esta función de forma nativa. Sin embargo muchas implementaciones proporciona alguna extensión para hacerlo.
presento una forma de lograrlo con Java y snakeyaml:1.24
(Biblioteca de Java para analizar / emitir archivos YAML) que permite la creación de una etiqueta personalizada YAML para lograr el siguiente objetivo (verá lo estoy usando para cargar conjuntos de pruebas definidos en varios archivos YAML y que hice que funcione como una lista de incluye un nodo test:
objetivo):
# ... yaml prev stuff
tests: !include
- '1.hello-test-suite.yaml'
- '3.foo-test-suite.yaml'
- '2.bar-test-suite.yaml'
# ... more yaml document
Esta es la clase de Java uno que permite el procesamiento de la etiqueta !include
. Los archivos se cargan de rutas de clases (recursos Maven directorio):
/**
* 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 , su manejo de yaml de manera indirecta que permita a los archivos YAML nido. El truco es hacer uso de la opción parameters
. por ejemplo:
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%"
El resultado será el mismo que:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
Probablemente no se admite cuando se le preguntó pregunta, pero se puede importar otro archivo YAML en una sola:
imports: [/your_location_to_yaml_file/Util.area.yaml]
A pesar de que no tengo ninguna referencia en línea, pero esto funciona para mí.