Wie kann ich eine YAML-Datei in ein anderes sind?
-
22-08-2019 - |
Frage
Also ich habe zwei YAML-Dateien, „A“ und „B“ und ich mag, dass die Inhalte von A nach innen B eingeführt werden, entweder gespleißt in die bestehenden Datenstruktur, wie ein Array oder als Kind eines Elements, wie der Wert für eine bestimmte Raute-Taste.
Ist das überhaupt möglich? Wie? Wenn nicht, alle Hinweise auf eine normative Referenz?
Lösung
Nein, YAML enthält keine Art von "Import" oder "umfasst" Anweisung.
Andere Tipps
Ihre Frage nicht für eine Python-Lösung, aber hier ist man mit PyYAML .
PyYAML können Sie benutzerdefinierte Konstrukteure (wie !include
) mit dem YAML Lader befestigen. Ich habe ein Root-Verzeichnis enthält, die so eingestellt werden können, dass diese Lösung relativen und absoluten Dateiverweis unterstützt.
Klasse-basierte Lösung
Hier ist eine Klasse-basierte Lösung, die die globale Wurzel Variable meiner ursprünglichen Antwort vermeidet.
Sehen Sie diese Kern für eine ähnliche, robustere Python 3-Lösung, die eine Metaklasse verwendet registrieren der benutzerdefinierten Konstruktor.
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)
Ein Beispiel:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
Nun können die Dateien geladen werden:
>>> 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]]}
Wenn Sie Symfony-Version von YAML verwenden, ist dies möglich, wie folgt aus:
imports:
- { resource: sub-directory/file.yml }
- { resource: sub-directory/another-file.yml }
Enthält nicht direkt in YAML unterstützt, soweit ich weiß, werden Sie einen Mechanismus bereitstellen, müssen jedoch selbst, dies in der Regel einfach zu tun.
habe ich YAML als Konfigurationssprache in meine Python-Anwendungen verwendet wird, und in diesem Fall definieren oft eine Konvention wie folgt aus:
>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
Da ist in meinem (Python) Code, den ich tun:
import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
cfg.update(yaml.load(open(inc)))
Der einzige Nachteil ist, dass Variablen in der beinhaltet immer die Variablen in Haupt außer Kraft gesetzt, und es gibt keine Möglichkeit, diesen Vorrang zu ändern, indem, wo die „enthält: Anweisung erscheint in der main.yml Datei
.Auf einer etwas anderen Stelle nicht YAML nicht unterstützt schließt als nicht wirklich entworfen wie ausschließlich als dateibasierte Auszeichnungs. Was wäre ein umfassen Mittel, wenn Sie es in einer Antwort auf eine AJAX-Anforderung bekommen?
Als Erweiterung @ Josh_Bode Antwort, hier ist meine eigene PyYAML Lösung, die den Vorteil hat, eine in sich geschlossene Unterklasse von yaml.Loader
zu sein. Es hängt nicht von irgendwelchen Modulebene Globals oder den globalen Zustand des yaml
Moduls zu ändern.
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
Für Python-Benutzer, können Sie versuchen, pyyaml-umfassen .
Installieren
pip install pyyaml-include
Verwendung
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)
Betrachten wir haben solche YAML Dateien:
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
-
1.yaml
‚s Inhalt:
name: "1"
-
2.yaml
‚s Inhalt:
name: "2"
Include-Dateien mit Namen
-
Auf Ebene:
Wenn
0.yaml
war:
!include include.d/1.yaml
Wir werden erhalten:
{"name": "1"}
-
In Abbildung:
Wenn
0.yaml
war:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
Wir werden erhalten:
file1:
name: "1"
file2:
name: "2"
-
In der Reihenfolge:
Wenn
0.yaml
war:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
Wir werden erhalten:
files:
- name: "1"
- name: "2"
i Hinweis :
Dateiname kann entweder absolut (wie
/usr/conf/1.5/Make.yml
) oder relativ (wie../../cfg/img.yml
).
Include-Dateien durch Platzhalter
Dateiname kann Shell-artige Platzhalter enthalten. Daten aus der Datei geladen (n) durch Platzhalter gefunden werden in einer Folge eingestellt werden.
Wenn 0.yaml
war:
files: !include include.d/*.yaml
Wir werden erhalten:
files:
- name: "1"
- name: "2"
i Hinweis :
- Für
Python>=3.5
, wennrecursive
Argument von!include
YAML Tag isttrue
, wird das Muster“**”
alle Dateien anzeigen lassen und Null oder mehr Verzeichnisse und Unterverzeichnisse.- Mit dem
“**”
Muster in großen Verzeichnisbäume können unmäßig viel Zeit wegen der rekursiven Suche verbrauchen.
Um recursive
Argument zu ermöglichen, werden wir den !include
Tag in Mapping
oder Sequence
Modus schreiben:
- Argumente in
Sequence
Modus:
!include [tests/data/include.d/**/*.yaml, true]
- Argumente in
Mapping
Modus:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
Leider ist dieses YAML bietet keine in seinem Standard.
Aber wenn Sie Ruby-verwenden, gibt es ein Juwel, die Funktionalität bereitstellt Sie fordern von der Rubin YAML Bibliothek erstreckt: https://github.com/entwanderer/yaml_extend
Ich denke, die Lösung, die von @ Maxy-B sieht gut aus. Allerdings ist es nicht für mich mit verschachtelten Einschlüsse gelingen. Zum Beispiel, wenn config_1.yaml umfasst config_2.yaml, die config_3.yaml umfasst es ein Problem mit dem Lader war. Wenn Sie jedoch einfach die neue Lader-Klasse selbst auf Lastpunkt, es funktioniert! Insbesondere dann, wenn wir die alte _include Funktion mit der sehr leicht modifizierte Version ersetzt werden:
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
Nach einigen Überlegungen bin ich mit den anderen Kommentaren, dass verschachtelter Laden für yaml im Allgemeinen nicht geeignet ist als der Eingangsstrom nicht eine Datei sein kann, aber es ist sehr nützlich!
Vielleicht inspirieren könnte, versuchen zu jbb Konventionen auszurichten:
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
Standard YAML 1.2 beinhaltet nicht nativ diese Funktion. Trotzdem viele Implementierungen einige Erweiterung bieten, dies zu tun.
stellt ich einen Weg, es mit Java zu erreichen und snakeyaml:1.24
(Java-Bibliothek zum Parsen / emittieren YAML-Dateien), die einen benutzerdefinierten YAML Tages ermöglicht die Erstellung das folgende Ziel zu erreichen (Sie werden sehen, ich verwende es Testsuiten zu laden definiert in mehreren YAML-Dateien und ich habe es als eine Liste arbeitet von für ein Ziel test:
Knoten enthält):
# ... yaml prev stuff
tests: !include
- '1.hello-test-suite.yaml'
- '3.foo-test-suite.yaml'
- '2.bar-test-suite.yaml'
# ... more yaml document
Hier ist die Ein-Klasse Java, die die !include
Tag ermöglicht die Verarbeitung. Die Dateien werden geladen von Classpath (Maven Ressourcen-Verzeichnis):
/**
* 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));
}
}
}
}
}
Mit Symfony , dessen Umgang mit yaml ermöglicht es Ihnen, indirekt zu nisten yaml Dateien. Der Trick ist die Verwendung der parameters
Option zu machen. zB:
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%"
Das Ergebnis wird das gleiche sein wie:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
Wahrscheinlich war es nicht unterstützt, wenn Fragen gestellt wurden, aber Sie können andere YAML-Datei in eine importieren:
imports: [/your_location_to_yaml_file/Util.area.yaml]
Obwohl ich keine Online-Referenz haben, aber das funktioniert für mich.