Как я могу включить файл YAML в другой? - PullRequest
232 голосов
/ 09 февраля 2009

Итак, у меня есть два файла YAML, «A» и «B», и я хочу, чтобы содержимое A было вставлено внутри B, либо встроено в существующую структуру данных, как массив, или как дочерний элемент элемента, как значение для определенного хеш-ключа.

Возможно ли это вообще? Как? Если нет, есть ли какие-либо указатели на нормативную ссылку?

Ответы [ 11 ]

267 голосов
/ 15 марта 2013

Нет, YAML не включает никаких операторов "import" или "include".

90 голосов
/ 06 марта 2012

Ваш вопрос не требует решения Python, но здесь используется PyYAML .

PyYAML позволяет подключать пользовательские конструкторы (например, !include) к загрузчику YAML. Я включил корневой каталог, который можно настроить так, чтобы это решение поддерживало относительные и абсолютные ссылки на файлы.

Решение на основе класса

Вот решение на основе классов, которое позволяет избежать глобальной корневой переменной моего исходного ответа.

См. Этот gist для аналогичного, более надежного решения 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]]}
25 голосов
/ 19 августа 2015

Если вы используете версию YAML Symfony, это возможно, например:

imports:
    - { resource: sub-directory/file.yml }
    - { resource: sub-directory/another-file.yml }
10 голосов
/ 23 января 2012

Включения не поддерживаются напрямую в YAML, насколько я знаю, вам придется самостоятельно создать механизм, однако это обычно легко сделать.

Я использовал YAML в качестве языка конфигурации в своих приложениях на Python, и в этом случае часто определяю такое соглашение:

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

Тогда в моем (python) коде я делаю:

import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
   cfg.update(yaml.load(open(inc)))

Единственным недостатком является то, что переменные во включениях всегда будут переопределять переменные в main, и нет никакого способа изменить этот приоритет, изменив место, где в файле main.yml появляется оператор "includes:"

В несколько ином аспекте, YAML не поддерживает включения, так как он на самом деле не разработан так же эксклюзивно, как разметка на основе файлов. Что будет означать включение, если вы получите его в ответ на запрос AJAX?

7 голосов
/ 03 сентября 2012

В продолжение ответа @ 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                                                      
1 голос
/ 02 апреля 2019

Для пользователей 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, если recursive аргумент тега !include YAML равен true, шаблон “**” будет сопоставлять любые файлы и ноль или более каталогов и подкаталогов.
  • Использование шаблона “**” в больших деревьях каталогов может потребовать чрезмерного количества времени из-за рекурсивного поиска.

Чтобы включить аргумент recursive, мы напишем тег !include в режиме Mapping или Sequence:

  • Аргументы в режиме Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Аргументы в режиме Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
1 голос
/ 27 февраля 2018

Я думаю, что решение, используемое @ maxy-B, выглядит великолепно. Однако для меня это не удалось с вложенными включениями. Например, если config_1.yaml включает config_2.yaml, который включает config_3.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 в целом, поскольку входной поток может быть не файлом, но это очень полезно!

1 голос
/ 05 августа 2017

К сожалению, в стандарте YAML этого нет.

Но если вы используете Ruby, есть гем, обеспечивающий требуемую функциональность за счет расширения библиотеки ruby ​​YAML: https://github.com/entwanderer/yaml_extend

0 голосов
/ 09 июля 2018

Возможно, это может вдохновить вас, попробуйте привести в соответствие с соглашениями 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

0 голосов
/ 12 февраля 2018

С 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"
...