Запись нескольких объектов JSON как одного объекта в один файл с Python

Я использую python, чтобы использовать API мастера, чтобы собрать некоторые факты обо всех хостах, о которых знает мастер. К сожалению, в API foreman v1 нет фактов get-all-hosts-fact (или чего-то подобного), поэтому мне приходится проходить по всем хостам и получать информацию. Это привело меня к досадной проблеме. Каждый вызов данного хоста возвращает объект JSON следующим образом:

{
  "host1.com": {
    "apt_update_last_success": "1452187711", 
    "architecture": "amd64", 
    "augeasversion": "1.2.0", 
    "bios_release_date": "06/03/2015", 
    "bios_vendor": "Dell Inc."
   }
}

Это совершенно нормально, проблема возникает, когда я добавляю информацию о следующем хосте. Затем я получаю файл JSON, который выглядит примерно так:

{
  "host1.com": {
    "apt_update_last_success": "1452187711", 
    "architecture": "amd64", 
    "augeasversion": "1.2.0", 
    "bios_release_date": "06/03/2015", 
    "bios_vendor": "Dell Inc."
}
}{
"host2.com": {
    "apt_update_last_success": "1452703454", 
    "architecture": "amd64", 
    "augeasversion": "1.2.0", 
    "bios_release_date": "06/03/2015", 
    "bios_vendor": "Dell Inc."
   }
}

Вот код, который делает это:

for i in hosts_data:
    log.info("Gathering host facts for host: {}".format(i['host']['name']))
    try:
        facts = requests.get(foreman_host+api+"hosts/{}/facts".format(i['host']['id']), auth=(username, password))
        if hosts.status_code != 200:
            log.error("Unable to connect to Foreman! Got retcode '{}' and error message '{}'"
            .format(hosts.status_code, hosts.text))
            sys.exit(1)
    except requests.exceptions.RequestException as e:
        log.error(e)
    facts_data = json.loads(facts.text)
    log.debug(facts_data)
    with open(results_file, 'a') as f:
        f.write(json.dumps(facts_data, sort_keys=True, indent=4))

Вот как мне нужен файл, чтобы он выглядел так:

{
"host1.com": {
    "apt_update_last_success": "1452187711",
    "architecture": "amd64",
    "augeasversion": "1.2.0",
    "bios_release_date": "06/03/2015",
    "bios_vendor": "Dell Inc."
},
"host2.com": {
    "apt_update_last_success": "1452703454",
    "architecture": "amd64",
    "augeasversion": "1.2.0",
    "bios_release_date": "06/03/2015",
    "bios_vendor": "Dell Inc."
  }
}

3 ответа

Решение

Было бы лучше собрать все ваши данные в один файл, а затем записать их все один раз, а не каждый раз в цикле.

d = {}
for i in hosts_data:
    log.info("Gathering host facts for host: {}".format(i['host']['name']))
    try:
        facts = requests.get(foreman_host+api+"hosts/{}/facts".format(i['host']['id']), auth=(username, password))
        if hosts.status_code != 200:
            log.error("Unable to connect to Foreman! Got retcode '{}' and error message '{}'"
            .format(hosts.status_code, hosts.text))
            sys.exit(1)
    except requests.exceptions.RequestException as e:
        log.error(e)
    facts_data = json.loads(facts.text)
    log.debug(facts_data)
    d.update(facts_data)  #add to dict
# write everything at the end
with open(results_file, 'a') as f:
    f.write(json.dumps(d, sort_keys=True, indent=4))

Вместо того, чтобы писать JSON внутри цикла, вставьте данные в dict с правильной структурой. Затем напишите это слово в JSON, когда цикл закончится.

Это предполагает, что ваш набор данных помещается в память.

Для обеспечения безопасности / согласованности вам необходимо загрузить старые данные, изменить их, а затем записать обратно.

Изменить текущий with а также write чтобы:

# If file guaranteed to exist, can use r+ and avoid initial seek
with open(results_file, 'a+') as f:
    f.seek(0)
    combined_facts = json.load(f)
    combined_facts.update(facts_data)
    f.seek(0)
    json.dump(combined_facts, f, sort_keys=True, indent=4)
    f.truncate()  # In case new JSON encoding smaller, e.g. due to replaced key

Примечание. Если возможно, вы хотите использовать ответ pault, чтобы минимизировать ненужные операции ввода-вывода, именно так вы и поступите, если поиск данных будет осуществляться по частям, с немедленным обновлением каждого элемента по мере его появления.

К вашему сведению, небезопасный способ состоит в том, чтобы в основном найти конечную фигурную скобку, удалить ее, а затем записать запятую, за которой следует новый JSON (удаляя ведущую фигурную скобку из ее представления JSON). Это намного менее интенсивно требует ввода-вывода, но также менее безопасно, не удаляет дубликаты, не сортирует хосты, вообще не проверяет входной файл и т. Д. Так что не делайте этого.

Другие вопросы по тегам