Modul 4 — Skriptování a automatizace

19. Práce s API a JSON/YAML v automatizačních skriptech

Volání REST API z Pythonu pomocí knihovny requests a čtení/zápis konfiguračních dat ve formátech JSON a YAML.

Odhadovaná délka studia: 55 minut · Stav: nehotovo

Technologie: Python

Úvod a kontext

Naprostá většina moderních nástrojů, které DevOps inženýr automatizuje —
cloudové platformy, monitoring, CI/CD systémy, Kubernetes — nabízí ovládání
přes REST API a ukládá konfiguraci ve formátu JSON nebo YAML.
Umět z Pythonu zavolat API, zpracovat odpověď a načíst/zapsat konfigurační
soubor je proto jedna z nejčastěji používaných dovedností v automatizačních
skriptech — mnohem častější než psaní vlastních algoritmů. Tato lekce
navazuje na základy Pythonu z minulé lekce a ukazuje, jak pracovat s daty,
která do skriptu přicházejí zvenčí.

Teorie

JSON a jeho reprezentace v Pythonu

JSON (JavaScript Object Notation) je textový formát pro strukturovaná
data — objekty ({}), pole ([]), řetězce, čísla, true/false,
null. Python má JSON podporu přímo ve standardní knihovně, modul json:

import json

data = {"jmeno": "web1", "port": 8080, "aktivni": True}

# Python objekt → JSON text
text = json.dumps(data, indent=2)

# JSON text → Python objekt (dict/list)
nacteno = json.loads(text)
print(nacteno["port"])   # 8080

json.dumps slouží k serializaci (Python → text), json.loads k
deserializaci (text → Python). Pro práci se soubory existují analogické
json.dump(data, soubor) a json.load(soubor), které rovnou čtou/zapisují
otevřený souborový objekt.

YAML a knihovna PyYAML

YAML je čitelnější alternativa k JSON, běžná v konfiguračních souborech
(Docker Compose, Kubernetes manifesty, GitHub Actions, Ansible). Na
rozdíl od JSON není součástí standardní knihovny — je potřeba doinstalovat
balíček pyyaml:

pip install pyyaml

Použití je analogické k modulu json:

import yaml

with open("konfig.yaml") as soubor:
    data = yaml.safe_load(soubor)

print(data["databaze"]["host"])

with open("vystup.yaml", "w") as soubor:
    yaml.safe_dump(data, soubor, allow_unicode=True)

yaml.safe_load (ne yaml.load) je důležité používat z bezpečnostních
důvodů — obyčejné yaml.load umí za určitých okolností spustit libovolný
Python kód zakódovaný v YAML souboru, pokud soubor pochází z nedůvěryhodného
zdroje. safe_load tuto možnost blokuje a měl by být výchozí volbou vždy,
pokud vývojář výslovně nepotřebuje pokročilé (a rizikové) vlastnosti.

Volání REST API pomocí knihovny requests

Standardní knihovna obsahuje modul urllib, ale v praxi se pro HTTP
požadavky téměř univerzálně používá balíček třetí strany requests — má
mnohem čitelnější API:

pip install requests
import requests

odpoved = requests.get(
    "https://api.example.com/servers",
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)
odpoved.raise_for_status()   # vyhodí výjimku, pokud stavový kód není 2xx
data = odpoved.json()        # rovnou naparsuje JSON tělo odpovědi

Klíčové prvky:

  • timeout=10 — request, který nikdy neskončí, dokáže zaseknout celý
    skript; timeout je proto v produkčním kódu povinný, ne volitelný.
  • raise_for_status() — bez tohoto volání skript tiše pokračuje i po
    chybě (např. 404 nebo 500), protože requests samo o sobě chybu
    nevyhazuje jen kvůli špatnému stavovému kódu.
  • .json() — pohodlná zkratka za json.loads(odpoved.text).

Odesílání dat (typicky vytváření nového záznamu přes API):

odpoved = requests.post(
    "https://api.example.com/servers",
    json={"jmeno": "web3", "port": 8080},
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)
odpoved.raise_for_status()

Parametr json= zajistí, že requests tělo požadavku automaticky
serializuje do JSON a nastaví správnou hlavičku Content-Type.

Zpracování chyb při volání API

Síťová volání selhávají mnohem častěji než lokální operace — výpadek sítě,
timeout, chybná autentizace, přetížený server. Robustní skript tyto stavy
ošetřuje explicitně:

try:
    odpoved = requests.get("https://api.example.com/status", timeout=10)
    odpoved.raise_for_status()
except requests.exceptions.Timeout:
    print("API neodpovědělo včas.")
except requests.exceptions.HTTPError as chyba:
    print(f"API vrátilo chybu: {chyba}")
except requests.exceptions.ConnectionError:
    print("Nepodařilo se připojit k API.")

Praktický příklad

Skript, který načte seznam serverů z YAML konfigurace, zjistí přes fiktivní
monitorovací API jejich aktuální stav a výsledek zapíše jako JSON report:

#!/usr/bin/env python3
"""Zkontroluje stav serverů z konfigurace přes monitorovací API."""

import json
import sys

import requests
import yaml

KONFIG_SOUBOR = "servery.yaml"
VYSTUP_SOUBOR = "report.json"
API_URL = "https://monitoring.example.com/api/stav"


def nacti_servery(cesta: str) -> list:
    with open(cesta) as soubor:
        data = yaml.safe_load(soubor)
    return data["servery"]


def zjisti_stav(jmeno: str) -> dict:
    odpoved = requests.get(f"{API_URL}/{jmeno}", timeout=10)
    odpoved.raise_for_status()
    return odpoved.json()


def main() -> int:
    try:
        servery = nacti_servery(KONFIG_SOUBOR)
    except FileNotFoundError:
        print(f"Konfigurační soubor {KONFIG_SOUBOR} nenalezen.")
        return 1

    report = []
    for server in servery:
        try:
            stav = zjisti_stav(server)
        except requests.exceptions.RequestException as chyba:
            print(f"Nepodařilo se zjistit stav {server}: {chyba}")
            stav = {"stav": "neznamy"}
        report.append({"server": server, **stav})

    with open(VYSTUP_SOUBOR, "w") as soubor:
        json.dump(report, soubor, indent=2, ensure_ascii=False)

    print(f"Report uložen do {VYSTUP_SOUBOR} ({len(report)} serverů).")
    return 0


if __name__ == "__main__":
    sys.exit(main())

Odpovídající konfigurace servery.yaml:

servery:
  - web1
  - web2
  - db1

Ukázka je záměrně strukturovaná do malých funkcí (nacti_servery,
zjisti_stav) — usnadňuje to testování jednotlivých kroků odděleně a
znovupoužití v jiném skriptu.

Shrnutí

Moduly json (vestavěný) a yaml (balíček pyyaml, vždy přes
safe_load) pokrývají naprostou většinu práce s konfiguračními a
výměnnými daty v automatizaci. Knihovna requests je standardní volba
pro volání REST API — vždy s explicitním timeout a raise_for_status(),
aby chyby nezůstaly tiše přehlédnuté. Kombinace obou (načtení konfigurace
z YAML, zavolání API, zápis výsledku do JSON) je jeden z nejběžnějších
vzorů automatizačních skriptů v provozu.

Kontrolní otázky

  1. Proč se v produkčním kódu vždy používá yaml.safe_load místo
    yaml.load?
  2. Co se stane, pokud requests.get(...) zavoláte bez parametru
    timeout, a proč je to problém pro automatizační skript spouštěný z
    cronu?
  3. K čemu slouží raise_for_status() a co by se stalo, kdyby skript toto
    volání vynechal a API vrátilo chybu 500?
  4. Upravte ukázkový skript tak, aby kromě zápisu do report.json vypsal
    na konci i počet serverů, u kterých se stav nepodařilo zjistit
    (stav: "neznamy").

Lekce na sebe nejsou zamčené — libovolnou lekci můžete otevřít i označit jako hotovou v jakémkoliv pořadí.