Wie kann ich eine YAML-Datei in eine andere aufnehmen?


288

Ich habe also zwei YAML-Dateien, "A" und "B", und ich möchte, dass der Inhalt von A in B eingefügt wird, entweder wie ein Array in die vorhandene Datenstruktur oder als untergeordnetes Element eines Elements wie der Wert für einen bestimmten Hash-Schlüssel.

Ist das überhaupt möglich? Wie? Wenn nicht, Hinweise auf eine normative Referenz?



1
Ich bin kürzlich auf HiYaPyCo für Python gestoßen , das genau das tut. Sie können verschiedene YAML-Dateien zusammenführen. Es ist ein sehr schönes Python-Modul, das es wert ist, kennengelernt zu werden.
Nowox

Antworten:


326

Nein, YAML enthält keine "import" - oder "include" -Anweisung.


8
Sie können einen! Include <filename> -Handler erstellen.
Clarkevans

5
@clarkevans sicher, aber dieses Konstrukt wäre "außerhalb" der YAML-Sprache.
Jameshfisher

2
Dies ist jetzt möglich. Ich habe unten eine Antwort hinzugefügt ... hoffe, es hilft.
Daveaspinall

1
Wenn Sie Rails verwenden, können Sie die ERB-Syntax <% = 'fdsa fdsa'%> einfügen, und es wird funktionieren
gleenn

9
Ich denke, diese Antwort sollte wie folgt umformuliert werden: "Nein, Standard-YAML enthält diese Funktion nicht. Trotzdem bieten viele Implementierungen eine Erweiterung, um dies zu tun."
Franklin Yu

112

Ihre Frage fragt nicht nach einer Python-Lösung, aber hier ist eine, die PyYAML verwendet .

Mit PyYAML können Sie benutzerdefinierte Konstruktoren (z. B. !include) an den YAML-Loader anhängen . Ich habe ein Stammverzeichnis eingefügt, das so eingestellt werden kann, dass diese Lösung relative und absolute Dateiverweise unterstützt.

Klassenbasierte Lösung

Hier ist eine klassenbasierte Lösung, die die globale Stammvariable meiner ursprünglichen Antwort vermeidet.

In dieser Übersicht finden Sie eine ähnliche, robustere Python 3-Lösung, die eine Metaklasse zum Registrieren des benutzerdefinierten Konstruktors verwendet.

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]

Jetzt können die Dateien geladen werden mit:

>>> 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]]}

Dies ist eine interessante Funktion, danke. Aber was ist der Zweck all dieser Manipulationen mit root / old_root? Ich nehme an, der includeFunktionscode kann vereinfacht werden: `def include (Loader, Node):" "Include eine andere YAML-Datei." "Dateiname = loader.construct_scalar (Node) data = yaml.load (open (Dateiname))`
Aliaksei Ramanau

Das globale Stammverzeichnis ist vorhanden, sodass relative Include-Arbeiten in jeder Tiefe enthalten, z. B. wenn eingeschlossene Dateien, die sich in einem anderen Verzeichnis befinden, eine Datei enthalten, die sich auf dieses Verzeichnis bezieht. Absolute Includes sollten auch funktionieren. Es gibt wahrscheinlich eine sauberere Möglichkeit, dies ohne eine globale Variable zu tun, möglicherweise mithilfe einer benutzerdefinierten yaml.Loader-Klasse.
Josh Bode

2
Ist es auch möglich, so etwas zu haben: foo.yaml: a: bla bar.yaml: `! Include foo.yaml b: blubb` Das Ergebnis wäre also:` {'a': bla, 'b': blubb}
Martin

3
Dies sollte die akzeptierte Antwort sein. Als Sicherheits-Nitpick sollten Sie außerdem yaml.safeload anstelle von yaml.load verwenden, um zu verhindern, dass speziell gestaltete Yaml Ihren Dienst besitzt.
Danielpops

1
@ JoshBode das sollte für dich funktionieren
danielpops

32

Wenn Sie die Symfony-Version von YAML verwenden , ist dies folgendermaßen möglich:

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

34
Dies ist spezifisch für die Interpretation von YAML durch Symfony und nicht für einen Teil von YAML.
Jameshfisher

9
Ja, deshalb habe ich den Link zu Symfony-Dokumenten gepostet. Die Frage lautet: "Ist das überhaupt möglich? Wie?" ... so geht das. Sehen Sie keinen Grund für eine Ablehnung.
Daveaspinall

4
Ich habe dich nicht herabgestimmt; Ich möchte nur darauf hinweisen, dass dies spezifisch für Symfony YAML ist.
Jameshfisher

9
Es gibt keine "Symfony-Version von YAML" ... dies ist einfach eine herstellerspezifische YAML-kompatible Bibliothek, die zusätzliche Inhalte enthält, die nicht Teil von YAML sind.
Dreftymac

3
Kein Grund, diese Antwort abzulehnen, wenn die "klassenbasierte" Antwort positiv bewertet wird.
Mikhail

13

Includes werden meines Wissens nicht direkt in YAML unterstützt. Sie müssen selbst einen Mechanismus bereitstellen. Dies ist jedoch im Allgemeinen einfach.

Ich habe YAML als Konfigurationssprache in meinen Python-Apps verwendet und in diesem Fall häufig eine Konvention wie die folgende definiert:

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

Dann mache ich in meinem (Python-) Code:

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 den Includes immer die Variablen in main überschreiben, und es gibt keine Möglichkeit, diese Priorität zu ändern, indem Sie ändern, wo die Anweisung "include:" in der Datei main.yml angezeigt wird.

In einem etwas anderen Punkt unterstützt YAML Includes nicht, da es nicht so exklusiv wie ein dateibasiertes Markup konzipiert ist. Was würde ein Include bedeuten, wenn Sie es als Antwort auf eine AJAX-Anfrage erhalten würden?


3
Dies funktioniert nur, wenn die yaml-Datei keine verschachtelte Konfiguration enthält.
Freiheit

10

Für Python-Benutzer können Sie pyyaml-include ausprobieren .

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)

Bedenken Sie, wir haben solche YAML- Dateien:

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • 1.yaml Inhalt:
name: "1"
  • 2.yaml Inhalt:
name: "2"

Dateien nach Namen einschließen

  • Auf der obersten Ebene:

    Wenn 0.yamlwar:

!include include.d/1.yaml

Wir werden .. bekommen:

{"name": "1"}
  • Im Mapping:

    Wenn 0.yamlwar:

file1: !include include.d/1.yaml
file2: !include include.d/2.yaml

Wir werden .. bekommen:

  file1:
    name: "1"
  file2:
    name: "2"
  • Der Reihe nach:

    Wenn 0.yamlwar:

files:
  - !include include.d/1.yaml
  - !include include.d/2.yaml

Wir werden .. bekommen:

files:
  - name: "1"
  - name: "2"

Hinweis :

Der Dateiname kann entweder absolut (wie /usr/conf/1.5/Make.yml) oder relativ (wie ../../cfg/img.yml) sein.

Fügen Sie Dateien mit Platzhaltern hinzu

Der Dateiname kann Platzhalter im Shell-Stil enthalten. Daten, die aus den von Platzhaltern gefundenen Dateien geladen wurden, werden in einer Reihenfolge festgelegt.

Wenn 0.yamlwar:

files: !include include.d/*.yaml

Wir werden .. bekommen:

files:
  - name: "1"
  - name: "2"

Hinweis :

  • Für Python>=3.5wenn recursiveArgument von !include YAML - Tag ist true, das Muster “**”wird alle Dateien übereinstimmen und keine oder mehrere Verzeichnisse und Unterverzeichnisse.
  • Die Verwendung des “**”Musters in großen Verzeichnisbäumen kann aufgrund der rekursiven Suche übermäßig viel Zeit in Anspruch nehmen.

Um das recursiveArgument zu aktivieren , schreiben wir das !includeTag in Mappingoder Sequencemode:

  • Argumente im SequenceModus:
!include [tests/data/include.d/**/*.yaml, true]
  • Argumente im MappingModus:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

Dies beantwortet die Frage nicht wirklich. Es handelt sich um eine Python-Lösung, die nicht das standardisierte YAML-Format verwendet.
Oligofren

@oligofren Benutzerdefinierte Tag-Handler sind eine Funktion von YAML, mit der Parser YAML erweitern können, um Typen anzugeben und benutzerdefinierte Verhaltensweisen wie diese zu implementieren. Es wäre ein langer Weg für die YAML-Spezifikation selbst, vorzuschreiben, wie die Aufnahme von Dateien mit allen unterschiedlichen Betriebssystempfadspezifikationen, Dateisystemen usw. funktionieren soll
Anton Strogonoff

@AntonStrogonoff Vielen Dank, dass Sie mich darauf aufmerksam gemacht haben. Könnten Sie mich auf einen solchen Platz im RFC hinweisen? Das Wort "Brauch" wird nicht erwähnt. Ref yaml.org/spec/1.2/spec.html
Oligofren

1
@oligofren Gern geschehen. Suchen Sie nach "anwendungsspezifischen" Tags .
Anton Strogonoff

8

Um die Antwort von @ Josh_Bode zu erweitern, hier ist meine eigene PyYAML-Lösung, die den Vorteil hat, eine in sich geschlossene Unterklasse von zu sein yaml.Loader. Dies hängt nicht von Globals auf Modulebene oder von der Änderung des globalen Status des yamlModuls ab.

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
Schließlich bekam um die Zugabe des basierten Class - Ansatz auf meine Antwort, aber sie schlugen mich auf den Stempel :) Hinweis: Wenn Sie yaml.load(f, IncludeLoader)innerhalb _includeSie vermeiden können , mit der Wurzel zu ersetzen. Wenn Sie dies nicht tun, funktioniert die Lösung nicht mehr als eine Ebene tief, da die enthaltenen Daten die reguläre yaml.LoaderKlasse verwenden.
Josh Bode

Ich musste das Schlüsselwort rootvon kwargsnach der Einstellung entfernen self.root, damit es mit Zeichenfolgen funktioniert. Ich habe den if-else-Block über den superAnruf verschoben . Vielleicht kann jemand anderes meinen Befund bestätigen oder mir zeigen, wie man die Klasse mit Strings und dem rootParameter verwendet.
Woltan

1
Leider funktioniert dies nicht mit Referenzen wie `` `enthalten: & INCLUDED! Include inner.yaml merge: <<: * INCLUDED` ``
antony

2

Ich mache einige Beispiele als Referenz.

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()

Ausgabe

shape name: Rectangle
shape area: 200
shape color: red
input file: /path/src/a

Update 2

und Sie können es so kombinieren

# 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

Ich finde die von @ maxy-B verwendete Lösung großartig. Mit verschachtelten Einschlüssen gelang es mir jedoch nicht. Wenn config_1.yaml beispielsweise config_2.yaml enthält, einschließlich config_3.yaml, ist ein Problem mit dem Loader aufgetreten. Wenn Sie die neue Loader-Klasse jedoch beim Laden einfach auf sich selbst zeigen, funktioniert dies! Insbesondere, wenn wir die alte _include-Funktion durch die sehr leicht modifizierte Version ersetzen:

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 Überlegung stimme ich den anderen Kommentaren zu, dass das verschachtelte Laden für yaml im Allgemeinen nicht geeignet ist, da der Eingabestream möglicherweise keine Datei ist, aber es ist sehr nützlich!


1

Der YML-Standard gibt keine Möglichkeit an, dies zu tun. Und dieses Problem beschränkt sich nicht auf YML. JSON hat die gleichen Einschränkungen.

Bei vielen Anwendungen, die YML- oder JSON-basierte Konfigurationen verwenden, tritt dieses Problem möglicherweise auf. Und wenn das passiert, bilden sie ihre eigene Konvention .

zB für Swagger-API-Definitionen:

$ref: 'file.yml'

zB für Docker komponieren Konfigurationen:

services:
  app:
    extends:
      file: docker-compose.base.yml

Wenn Sie den Inhalt einer XML-Datei wie einen Inhaltsbaum in mehrere Dateien aufteilen möchten, können Sie alternativ Ihre eigene Ordnerstrukturkonvention definieren und ein (vorhandenes) Zusammenführungsskript verwenden.



0

Standard YAML 1.2 enthält diese Funktion nicht nativ. Trotzdem bieten viele Implementierungen eine Erweiterung, um dies zu tun.

Ich präsentiere eine Möglichkeit, dies mit Java und snakeyaml:1.24(Java-Bibliothek zum Parsen / Ausgeben von YAML-Dateien) zu erreichen, mit der ein benutzerdefiniertes YAML-Tag erstellt werden kann, um das folgende Ziel zu erreichen (Sie werden sehen, dass ich damit Testsuiten lade, die in mehreren YAML-Dateien definiert sind und dass ich es als Liste von Includes für einen test:Zielknoten funktionieren ließ ):

# ... 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 das Java einer Klasse, mit dem das !includeTag verarbeitet werden kann. Dateien werden aus dem Klassenpfad (Maven-Ressourcenverzeichnis) geladen:

/**
 * 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

Mit Yglu können Sie andere Dateien wie folgt importieren:

A. yaml

foo: !? $import('B.yaml')

B. yaml

bar: Hello
$ yglu A.yaml
foo:
  bar: Hello

Als $importFunktion können Sie auch einen Ausdruck als Argument übergeben:

  dep: !- b
  foo: !? $import($_.dep.toUpper() + '.yaml')

Dies würde die gleiche Ausgabe wie oben ergeben.

Haftungsausschluss: Ich bin der Autor von Yglu.


-1

Mit Symfony können Sie durch die Behandlung von Yaml indirekt Yaml-Dateien verschachteln. Der Trick besteht darin, die parametersOption zu nutzen. z.B:

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 ist das gleiche wie:

whatever:
    thing:
        option: "value"
        foo:
            - "bar"
            - "baz"
    other_thing:
        option: "value"
        foo:
            - "bar"
            - "baz"

-6

Wahrscheinlich wurde es nicht unterstützt, als eine Frage gestellt wurde, aber Sie können eine andere YAML-Datei in eine importieren:

imports: [/your_location_to_yaml_file/Util.area.yaml]

Ich habe zwar keine Online-Referenz, aber das funktioniert für mich.


4
Dies macht überhaupt keine Einbeziehung. Es wird eine Zuordnung mit einer Sequenz erstellt, die aus einer einzelnen Zeichenfolge "/your_location_to_yaml_file/Util.area.yaml" als Wert für den Schlüssel besteht imports.
Anthon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.