YAML dosyasını başka birine nasıl ekleyebilirim?


288

Bu yüzden "Y" ve "B" olmak üzere iki YAML dosyam var ve A'nın içeriğinin B içine yerleştirilmesini istiyorum, ya bir dizi gibi mevcut veri yapısına eklenmiş ya da değer gibi bir öğenin alt öğesi olarak belirli bir karma anahtar için.

Bu hiç mümkün mü? Nasıl? Değilse, normatif bir referansa işaret eden var mı?



1
Son zamanlarda tam olarak bunu yapan Python için HiYaPyCo içine çarptı . Farklı YAML dosyalarını bir araya getirebilirsiniz. Bilmeye değer çok güzel bir Python modülü.
nowox

Yanıtlar:


326

Hayır, YAML herhangi bir "import" veya "include" ifadesi içermez.


8
! İnclude <filename> işleyicisi oluşturabilirsiniz.
clarkevans

5
@clarkevans emin, ama bu yapı YAML dilinin "dışında" olacaktır.
jameshfisher

2
Bu artık mümkün. Aşağıya bir cevap ekledim ... umarım yardımcı olur.
daveaspinall

1
Rails kullanıyorsanız, <% = '
fdsa fdsa

9
Bence bu cevap "Hayır, standart YAML bu işlevi içermiyor."
Franklin Yu

113

Sorunuz bir Python çözümü istemiyor, ancak burada PyYAML kullanan bir çözüm var .

PyYAML !include, YAML yükleyiciye özel yapıcılar (örneğin ) eklemenizi sağlar . Ben bu çözüm göreli ve mutlak dosya referanslarını destekleyecek şekilde ayarlanabilir bir kök dizin dahil ettik.

Sınıf Tabanlı Çözüm

İşte orijinal yanıtımın global kök değişkeninden kaçınan sınıf tabanlı bir çözüm.

Özel yapıcıyı kaydetmek için bir metasınıf kullanan benzer, daha sağlam bir Python 3 çözümü için bu özete bakın .

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)

Bir örnek:

foo.yaml

a: 1
b:
    - 1.43
    - 543.55
c: !include bar.yaml

bar.yaml

- 3.6
- [1, 2, 3]

Artık dosyalar şu şekilde yüklenebilir:

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

Bu ilgi çekici bir özellik, teşekkürler. Peki root / old_root ile yapılan tüm bu manipülasyonların amacı nedir? includeİşlev kodunun basitleştirilebileceğini düşünüyorum: `def include (yükleyici, düğüm):" "" Başka bir YAML dosyası ekleyin. "" "Dosyaadı = loader.construct_scalar (düğüm) data = yaml.load (open (dosyaadı))`
Aliaksei Ramanau

Kök global, göreceli herhangi bir derinlikte çalışmayı içerecek şekilde, örneğin farklı bir dizinde oturan dosyalar söz konusu dizine göre bir dosya içerdiğinde bulunur. Mutlak içerikler de çalışmalıdır. Muhtemelen bunu özel bir yaml.Loader sınıfı kullanarak küresel bir değişken olmadan yapmanın daha temiz bir yolu vardır.
Josh Bode

2
Bunun gibi bir şeye sahip olmak da mümkün mü: foo.yaml: a: bla bar.yaml: `! İnclude foo.yaml b: blubb` Böylece sonuç şöyle olur:` {'a': bla, 'b': blubb}
Martin

3
Bu kabul edilen cevap olmalı. Ayrıca, bir güvenlik nitpick, özel hazırlanmış yaml hizmetinize sahip olmasını önlemek için yaml.load yerine yaml.safeload kullanmalısınız.
danielpops

1
@JoshBode bunun sizin için çalışması gerekir: gist.github.com/danielpops/5a0726f2fb6288da749c4cd604276be8
danielpops

32

Symfony'nin YAML sürümünü kullanıyorsanız , bu mümkündür:

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

34
Bu Symfony'nin YAML'ın bir parçası olmaktan ziyade YAML'yi nasıl yorumladığına özgüdür.
jameshfisher

9
Evet, bu yüzden Symfony belgelerine bağlantı gönderdim. Soru "Bu mümkün mü? Nasıl?" Diye soruyor. İniş için bir neden görmüyorum.
daveaspinall

4
Seni küçümsemedim; Sadece bunun Symfony YAML'a özgü olduğunu belirtiyorum.
jameshfisher

9
"YAML'ın Symfony sürümü" yoktur ... bu sadece YAML'nin bir parçası olmayan ekstra şeyler içeren satıcıya özgü YAML uyumlu bir kütüphanedir.
dreftymac

3
"Sınıf tabanlı" cevap kaldırılırsa bu cevabı küçümsemek için bir neden yoktur.
Mikhail

13

İçerdiği bildiğim kadarıyla YAML'de doğrudan desteklenmiyor, ancak kendiniz bir mekanizma sağlamanız gerekecek, ancak bu genellikle kolaydır.

Python uygulamalarımda bir yapılandırma dili olarak YAML kullandım ve bu durumda genellikle böyle bir kural tanımlarım:

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

Sonra benim (python) kodunda:

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

Tek tarafı, içerilen değişkenlerin ana değişkenleri her zaman geçersiz kılacağı ve main.yml dosyasında "include: ifadesinin göründüğü yeri değiştirerek bu önceliği değiştirmenin bir yolu olmamasıdır.

Biraz farklı bir noktada, YAML, sadece dosya tabanlı bir işaretleme kadar tasarlanmadığından, desteklemez. AJAX isteğine yanıt olarak bu öğeyi dahil etmek ne anlama gelir?


3
bu yalnızca yaml dosyası iç içe yapılandırma içermiyorsa çalışır.
Özgürlük

10

Python kullanıcıları için pyyaml-include'u deneyebilirsiniz .

Yüklemek

pip install pyyaml-include

kullanım

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)

Bu tür YAML dosyalarımız olduğunu düşünün :

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • 1.yaml adlı kullanıcının içeriği:
name: "1"
  • 2.yaml adlı kullanıcının içeriği:
name: "2"

Dosyaları ada göre ekle

  • Üst düzeyde:

    Öyleyse 0.yaml:

!include include.d/1.yaml

Şunları elde edeceğiz:

{"name": "1"}
  • Haritalamada:

    Öyleyse 0.yaml:

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

Şunları elde edeceğiz:

  file1:
    name: "1"
  file2:
    name: "2"
  • Sırayla:

    Öyleyse 0.yaml:

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

Şunları elde edeceğiz:

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

Not :

Dosya adı mutlak (like /usr/conf/1.5/Make.yml) veya göreli (like ../../cfg/img.yml) olabilir.

Joker karakterlere dosya ekle

Dosya adı kabuk tarzı joker karakterler içerebilir. Joker karakterler tarafından bulunan dosyalardan yüklenen veriler sırayla ayarlanır.

Öyleyse 0.yaml:

files: !include include.d/*.yaml

Şunları elde edeceğiz:

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

Not :

  • Çünkü Python>=3.5, YAML etiketinin recursiveargümanı ise , kalıp tüm dosyalarla ve sıfır veya daha fazla dizin ve alt dizinle eşleşir.!include true“**”
  • Kullanılması “**”büyük dizin ağaçlarında deseni nedeniyle özyinelemeli arama zaman bir aşırı miktarda tüketebilir.

recursiveArgümanı etkinleştirmek için !includeetiketi Mappingveya Sequencemodunda yazacağız :

  • Moddaki bağımsız değişkenler Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Moddaki bağımsız değişkenler Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

Bu aslında soruyu cevaplamıyor. Standart YAML formatını kullanan bir Python çözümü ile ilgilidir.
oligofren

@oligofren Özel etiket işleyicileri, ayrıştırıcıların YAML'yi türleri belirtmek ve bunlar gibi özel davranışlar uygulamak için genişletmelerine olanak tanıyan bir YAML özelliğidir. YAML spesifikasyonunun kendisi, dosya dahil etmenin tüm farklı OS yolu özellikleri, dosya sistemleri
vb.İle

@AntonStrogonoff Bunu dikkatime sunduğunuz için teşekkür ederim. Beni RFC'de böyle bir yere yönlendirebilir misiniz? "Özel" kelimesinden bahsedilmez. Ref yaml.org/spec/1.2/spec.html
oligofren

1
@oligofren Bir şey değil. “Uygulamaya özel” etiketleri arayın .
Anton Strogonoff

8

@ Josh_Bode'un cevabına genişleyen, burada kendi kendine yeten bir alt sınıf olma avantajına sahip kendi PyYAML çözümüm yaml.Loader. Modül düzeyinde globallere veya yamlmodülün global durumunu değiştirmeye bağlı değildir .

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
Sonunda cevabım için sınıf tabanlı yaklaşımı eklemek için var, ama beni yumruk attı :) Not: Eğer yaml.load(f, IncludeLoader)içinde kullanırsanız _include, kök değiştirmek zorunda kalabilirsiniz. Ayrıca, bunu yapmazsanız, içerilen veriler normal yaml.Loadersınıfı kullandığından çözüm birden fazla derinlemesine çalışmayacaktır .
Josh Bode

Ben anahtar kelimeyi kaldırmak zorunda rootarasında kwargsayarladıktan sonra self.rootdizeleri ile çalışan almak için. İf-else bloğunu superçağrının üzerine taşıdım . Belki bir başkası bulmamı onaylayabilir ya da sınıfı dize ve rootparametre ile nasıl kullanacağımı gösterebilir .
Woltan

1
Ne yazık ki, bu `` dahil: & INCLUDED! Gibi inner.yaml birleştirme: <<: * INCLUDED ``
antony

2

Ben referans için bazı örnekler yapmak.

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

çıktı

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

Güncelleme 2

ve bu şekilde birleştirebilirsiniz

# 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

@ Maxy-B tarafından kullanılan çözüm harika görünüyor. Ancak, içiçe kapanımlar ile benim için başarılı olmadı. Örneğin config_1.yaml, config_3.yaml dosyasını içeren config_2.yaml içeriyorsa, yükleyicide bir sorun vardı. Ancak, yeni yükleyici sınıfını yükte kendisine işaret ederseniz, işe yarar! Özellikle, eski _include işlevini biraz değiştirilmiş sürümle değiştirirsek:

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

Yansıtma üzerine diğer yorumları kabul ediyorum, giriş akışı bir dosya olmayabilir, ancak çok yararlı olduğu için iç içe yüklemenin genel olarak yaml için uygun olmadığını!


1

YML standardı bunu yapmanın bir yolunu belirtmez. Ve bu sorun kendisini YML ile sınırlamaz. JSON aynı sınırlamalara sahiptir.

YML veya JSON tabanlı yapılandırmalar kullanan birçok uygulama nihayetinde bu sorunla karşılaşır. Ve bu olduğunda, kendi sözleşmelerini oluştururlar .

örneğin, swagger API tanımları için:

$ref: 'file.yml'

örneğin liman işçisi oluşturma yapılandırmaları için:

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

Alternatif olarak, bir yml dosyasının içeriğini bir içerik ağacı gibi birden çok dosyaya bölmek isterseniz, kendi klasör yapısı kuralınızı tanımlayabilir ve (mevcut) birleştirme komut dosyası kullanabilirsiniz.



0

Standart YAML 1.2 yerel olarak bu özelliği içermez. Bununla birlikte, birçok uygulama bunu yapmak için bir miktar uzantı sağlamaktadır.

snakeyaml:1.24Aşağıdaki hedefe ulaşmak için özel bir YAML etiketi oluşturmaya izin veren Java ve (YAML dosyalarını ayrıştırmak / yaymak için Java kütüphanesi) ile elde etmenin bir yolunu sunuyorum (birkaç YAML dosyasında tanımlanan test paketlerini yüklemek için kullanacağımı göreceksiniz) ve ben bir hedef test:düğüm için içerir bir liste olarak çalışması yaptı ):

# ... yaml prev stuff

tests: !include
  - '1.hello-test-suite.yaml'
  - '3.foo-test-suite.yaml'
  - '2.bar-test-suite.yaml'

# ... more yaml document

!includeEtiketin işlenmesine izin veren tek sınıf Java . Dosyalar sınıfyolundan yüklenir (Maven kaynakları dizini):

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

Yglu ile aşağıdaki diğer dosyaları içe aktarabilirsiniz:

A.yaml

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

B.yaml

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

$importBir işlev olduğu gibi , bir ifadeyi bağımsız değişken olarak da iletebilirsiniz:

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

Bu, yukarıdaki ile aynı çıktıyı verecektir.

Feragatname: Yglu'nun yazarıyım.


-1

İle Symfony'de , YAML onun taşıma dolaylı yuva yaml dosyaları ekleyebileceksiniz. İşin püf noktası parametersseçeneği kullanmaktır . Örneğin:

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%"

Sonuç şu şekilde olacaktır:

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

-6

Muhtemelen soru sorulduğunda desteklenmemiştir, ancak diğer YAML dosyasını birine aktarabilirsiniz:

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

Çevrimiçi referansım yok ama bu benim için çalışıyor.


4
Bu hiçbir şey dahil değildir. Anahtar için değer olarak tek bir dizeden "/your_location_to_yaml_file/Util.area.yaml" oluşan bir dizi ile bir eşleme oluşturur imports.
Anthon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.