Загрузка и выгрузка нескольких файлов yaml с помощью ruamel.yaml (python) - PullRequest
0 голосов
/ 23 октября 2018

Использование python 2 (atm) и ruamel.yaml 0.13.14 (RedHat EPEL)

В настоящее время я пишу некоторый код для загрузки определений yaml, но они разбиты на несколько файлов.Редактируемая пользователем часть содержит, например,

users:
  xxxx1:
    timestamp: '2018-10-22 11:38:28.541810'
    << : *userdefaults
  xxxx2:
    << : *userdefaults
    timestamp: '2018-10-22 11:38:28.541810'

значения по умолчанию хранятся в другом файле, который нельзя редактировать:

userdefaults: &userdefaults
    # Default values for user settings
    fileCountQuota: 1000
    diskSizeQuota: "300g"

Я могу обрабатывать их вместе, загружая оба и объединяястрок, а затем пропустив их через merged_data = list(yaml.load_all("{}\n{}".format(defaults_data, user_data), Loader=yaml.RoundTripLoader)), который правильно разрешает все.(когда я не использую RoundTripLoader, я получаю сообщения об ошибках, которые не могут быть разрешены, что является нормальным)

Теперь я хочу сделать некоторые обновления с помощью кода Python (например, обновить временную метку), и для этого мне нужнопросто напишите обратно пользовательскую часть.И вот тут все становится волосатым.Я полагаю, я не нашел способа написать этот документ yaml, а не оба.

Ответы [ 2 ]

0 голосов
/ 23 октября 2018

Прежде всего, если в вашем файле по умолчанию нет нескольких документов, вам не нужно использовать load_all, так как вы не объединяете два документа в поток из нескольких документов.Если бы вы использовали строку формата с маркером конца документа ("{}\n...\n{}") или с маркером конца директивы ("{}\n---\n{}"), ваши псевдонимы не переносились бы из одного документа в другой согласно спецификации YAML:

Для узла псевдонима является ошибкой использование привязки, которая ранее не встречалась в документе.

Якорь должен быть в документе, а не только впоток (который может состоять из нескольких документов).


Я попробовал несколько фокус-покусов, предварительно заполнив уже представленный словарь привязанных узлов:

import sys
import datetime
from ruamel import yaml

def load():
    with open('defaults.yaml') as fp:
        defaults_data = fp.read()
    with open('user.yaml') as fp:
        user_data = fp.read()
    merged_data = yaml.load("{}\n{}".format(defaults_data, user_data), 
                            Loader=yaml.RoundTripLoader)
    return merged_data

class MyRTDGen(object):
    class MyRTD(yaml.RoundTripDumper):
        def __init__(self, *args, **kw):
            pps = kw.pop('pre_populate', None)
            yaml.RoundTripDumper.__init__(self, *args, **kw)
            if pps is not None:
                for pp in pps:
                    try:
                        anchor = pp.yaml_anchor()
                    except AttributeError:
                        anchor = None
                    node = yaml.nodes.MappingNode(
                        u'tag:yaml.org,2002:map', [], flow_style=None, anchor=anchor)
                    self.represented_objects[id(pp)] = node

    def __init__(self, pre_populate=None):
        assert isinstance(pre_populate, list)
        self._pre_populate = pre_populate 

    def __call__(self, *args, **kw):
        kw1 = kw.copy()
        kw1['pre_populate'] = self._pre_populate
        myrtd = self.MyRTD(*args, **kw1)
        return myrtd


def update(md, file_name):
    ud = md.pop('userdefaults')
    MyRTD = MyRTDGen([ud])
    yaml.dump(md, sys.stdout, Dumper=MyRTD)
    with open(file_name, 'w') as fp:
        yaml.dump(md, fp, Dumper=MyRTD)

md = load()
md['users']['xxxx2']['timestamp'] = str(datetime.datetime.utcnow())
update(md, 'user.yaml')

Поскольку на основе PyYAMLAPI требует класс вместо объекта, вам нужно использовать генератор классов, который на самом деле добавляет элементы данных для предварительного заполнения на лету из-за yaml.load().

Но это не работает, так какузел записывается с привязкой только после того, как будет определено, что привязка используется (т. е. имеется вторая ссылка).Таким образом, первый ключ слияния записывается как якорь.И хотя я достаточно хорошо знаком с базой кода, я не смог заставить ее работать должным образом в разумные сроки.

Поэтому вместо этого я бы просто полагался на тот факт, что существует только один ключ, который соответствуетпервый ключ users.yaml на корневом уровне дампа объединенного обновленного файла и удалите все, что до этого.

import sys
import datetime
from ruamel import yaml

with open('defaults.yaml') as fp:
    defaults_data = fp.read()
with open('user.yaml') as fp:
    user_data = fp.read()
merged_data = yaml.load("{}\n{}".format(defaults_data, user_data), 
                        Loader=yaml.RoundTripLoader)

# find the key
for line in user_data.splitlines():
    line = line.split('# ')[0].rstrip()  # end of line comment, not checking for strings
    if line and line[-1] == ':' and line[0] != ' ':
        split_key = line
        break

merged_data['users']['xxxx2']['timestamp'] = str(datetime.datetime.utcnow())

buf = yaml.compat.StringIO()
yaml.dump(merged_data, buf, Dumper=yaml.RoundTripDumper)
document = split_key + buf.getvalue().split('\n' + split_key)[1]
sys.stdout.write(document)

, что дает:

users:
  xxxx1:
    <<: *userdefaults
    timestamp: '2018-10-22 11:38:28.541810'
  xxxx2:
    <<: *userdefaults
    timestamp: '2018-10-23 09:59:13.829978'

IЯ должен был сделать virtualenv, чтобы удостовериться, что смогу выполнить вышеизложенное с ruamel.yaml==0.13.14Эта версия с тех пор, как я был еще молод (я не буду утверждать, что был невиновен).С тех пор было выпущено более 85 выпусков библиотеки.

Я могу понять, что в настоящий момент вы можете не запускать ничего, кроме Python2, и не можете скомпилировать / использовать более новую версию.Но то, что вам действительно нужно сделать, это установить virtualenv (это можно сделать с помощью EPEL, но также без дальнейшего «загрязнения» установки вашей системы), сделать virtualenv для кода, который вы разрабатываете, и установить последнюю версию ruamel.yaml (иваши другие библиотеки) там.Вы также можете сделать это, если вам нужно распространить свое программное обеспечение на другие системы, просто установите туда virtualenv.

У меня есть все мои утилиты в /opt/util, и они управляются virtualenvutilsобертка вокруг virtualenv.

0 голосов
/ 23 октября 2018

Для записи пользовательской части вам придется вручную разделить вывод yaml.dump() мультифайлового вывода и записать соответствующую часть обратно в файл yaml пользователей.

import datetime
import StringIO

import ruamel.yaml

yaml = ruamel.yaml.YAML(typ='rt')
data = None

with open('defaults.yaml', 'r') as defaults:
    with open('users.yaml', 'r') as users:
        raw = "{}\n{}".format(''.join(defaults.readlines()), ''.join(users.readlines()))
        data = list(yaml.load_all(raw))

data[0]['users']['xxxx1']['timestamp'] = datetime.datetime.now().isoformat()

with open('users.yaml', 'w') as outfile:
    sio = StringIO.StringIO()
    yaml.dump(data[0], sio)
    out = sio.getvalue()
    outfile.write(out.split('\n\n')[1]) # write the second part here as this is the contents of users.yaml
...