Zum Inhalt springen
aviral gupta

// A5.2 · ca. 30 Min. · Vertiefung

Konfiguration mit tomllib lesen

Nach dieser Lektion lesen Sie eine TOML-Datei oder einen TOML-String mit tomllib, sagen den Python-Typ jedes Werts vorher und prüfen das Ergebnis mit Fehlermeldungen, die den fehlerhaften Schlüssel nennen.

Lektion 2 von 5 in A5 Modernes Python und sicherer Code

Danach können Sie

  • TOML mit tomllib.loads parsen, und mit tomllib.load aus einer im Binärmodus geöffneten Datei
  • Die Python-Typen von TOML-Werten vorhersagen: Tabellen zu dicts, Arrays zu Listen, Daten zu datetime-Objekten
  • Eine geparste Konfiguration prüfen: Pflichtschlüssel, Typen, Bereiche und Standardwerte, mit klaren Fehlern
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus der vorigen Lektion: Was gibt das aus?

    port = 8080
    print(t"{port}".values)
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Was gibt das aus?

    import tomllib
    
    data = tomllib.loads("port = 8080\ndebug = true\n")
    print(data, type(data["port"]).__name__)
  3. Üben · Aufgabe 3 von 7

    Setzen Sie den Modus ein, damit tomllib.load die Datei lesen kann.

    import tomllib
    
    with open("config.toml", ____) as f:
        config = tomllib.load(f)
    print(config["name"])
    with open("config.toml", ) as f:
  4. Üben · Aufgabe 4 von 7

    Ein Datum und ein Array von Tabellen. Was gibt das aus?

    import tomllib
    
    doc = """
    released = 2026-09-29
    
    [[server]]
    name = "a"
    
    [[server]]
    name = "b"
    """
    data = tomllib.loads(doc)
    print(type(data["released"]).__name__, type(data["server"]).__name__, data["server"][1])
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jeder TOML-Zeile den Python-Typ zu, den tomllib für ihren Wert liefert.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Jemand hat workers = true statt einer Zahl geschrieben. Was gibt das aus?

    import tomllib
    
    config = tomllib.loads("workers = true\n")
    workers = config["workers"]
    if isinstance(workers, int):
        print("workers:", workers + 1)
    else:
        print("not a number")
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe. Schreiben Sie load_settings(text) mit DEFAULTS = {"host": "localhost", "port": 8000, "debug": False}. Die Funktion parst den TOML-Text, löst ValueError aus, der jeden Schlüssel nennt, der nicht in DEFAULTS steht, und gibt die Standardwerte zurück, überschrieben durch die geladenen Werte. Probieren Sie "port = 9000\ndebug = true\n" und den Tippfehler "prot = 9000\n".

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Die Konfiguration eines Dienstes lesen

config.toml beschreibt einen kleinen Dienst: einen Titel, ein Veröffentlichungsdatum, eine Tabelle [database] und zwei Einträge [[servers]]. main.py öffnet die Datei im Binärmodus, lädt sie und liest Werte verschiedener Typen. Am Ende parst es ein fehlerhaftes Dokument mit demselben Schlüssel zweimal und gibt Meldung und Zeile aus, die TOMLDecodeError mitbringt. Beide Dateien stehen im Editor: Ändern Sie config.toml und starten Sie erneut.

main.py

import tomllib

with open("config.toml", "rb") as f:  # binary mode: tomllib decodes UTF-8 itself
    config = tomllib.load(f)

print(config["title"])
print(config["database"])
print(type(config["released"]).__name__, config["released"].year)
for server in config["servers"]:
    print(f"{server['name']}: {server['ip']}")

try:
    tomllib.loads("port = 80\nport = 81\n")
except tomllib.TOMLDecodeError as err:
    print("error:", err.msg, "at line", err.lineno)

config.toml

# Settings for the report service
title = "Report service"
released = 2026-09-29

[database]
host = "db.local"
port = 5432
enabled = true

[[servers]]
name = "alpha"
ip = "10.0.0.1"

[[servers]]
name = "beta"
ip = "10.0.0.2"

Ausführen mit

python main.py

Ausgabe

Report service
{'host': 'db.local', 'port': 5432, 'enabled': True}
date 2026
alpha: 10.0.0.1
beta: 10.0.0.2
error: Cannot overwrite a value at line 2
  • Die Tabelle [database] kommt als dict an, 5432 als int und true als True.
  • released ist ein datetime.date, .year funktioniert also ohne eigenes Parsen.
  • [[servers]] liefert eine Liste von dicts, eines je Kopf, in Dateireihenfolge.
  • err.msg und err.lineno sind die Attribute von TOMLDecodeError seit 3.14.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Einen Port und einen exakten Preis lesen

Korrigieren Sie beide Funktionen in main.py. read_port(path) liefert server.port aus einer TOML-Datei. read_price(path) liefert den Preis price der obersten Ebene als exaktes Decimal, sodass price = 0.1 Decimal("0.1") ergibt, nicht den float 0.1. Der Starter öffnet die Dateien im Textmodus, und das lehnt tomllib ab.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    tomllib.load will eine Binärdatei: open(path, "rb"). Im Binärmodus gibt es kein encoding-Argument.

  2. Hinweis 2

    Verschachtelte Tabellen sind verschachtelte dicts: config["server"]["port"].

  3. Hinweis 3

    tomllib.load(f, parse_float=Decimal) ruft Decimal mit dem Text jeder TOML-Kommazahl auf.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import tomllib
from decimal import Decimal


def read_port(path: str) -> int:
    """Return server.port from the TOML file at path."""
    with open(path, "rb") as f:
        config = tomllib.load(f)
    return config["server"]["port"]


def read_price(path: str) -> Decimal:
    """Return price from the TOML file at path, as an exact Decimal."""
    with open(path, "rb") as f:
        config = tomllib.load(f, parse_float=Decimal)
    return config["price"]
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

import tomllib
from decimal import Decimal


def read_port(path: str) -> int:
    """Return server.port from the TOML file at path."""
    with open(path, encoding="utf-8") as f:
        config = tomllib.load(f)
    return config["server"]["port"]


def read_price(path: str) -> Decimal:
    """Return price from the TOML file at path, as an exact Decimal."""
    with open(path, encoding="utf-8") as f:
        config = tomllib.load(f)
    return config["price"]

test_main.py

from decimal import Decimal

from main import read_port, read_price


def write(name, text):
    with open(name, "w", encoding="utf-8") as f:
        f.write(text)
    return name


def test_port():
    """server.port wird aus der Datei gelesen"""
    path = write("app.toml", '[server]\nhost = "0.0.0.0"\nport = 8080\n')
    got = read_port(path)
    assert got == 8080, f"read_port lieferte {got!r}, erwartet 8080"


def test_other_file():
    """Eine andere Datei liefert ihren eigenen Port"""
    path = write("other.toml", "[server]\nport = 9000\n")
    got = read_port(path)
    assert got == 9000, f"read_port lieferte {got!r}, erwartet 9000"


def test_price_exact():
    """price = 0.1 kommt als Decimal('0.1') zurück"""
    path = write("shop.toml", "price = 0.1\n")
    got = read_price(path)
    assert isinstance(got, Decimal) and got == Decimal("0.1"), f"read_price lieferte {got!r}, erwartet Decimal('0.1')"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

Die Einstellungen prüfen

Vervollständigen Sie load_settings(text). Parsen Sie das TOML und lösen Sie ValueError mit dem Schlüssel in der Meldung aus, wenn: ein Schlüssel nicht in ALLOWED steht; name fehlt oder kein nicht leerer str ist; port kein int von 1 bis 65535 ist (bool zählt nicht); debug vorhanden, aber kein bool ist. Geben Sie {"name": ..., "port": ..., "debug": ...} zurück, debug ohne Angabe False.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    set(config) - ALLOWED liefert die unbekannten Schlüssel. Ist die Menge nicht leer, lösen Sie aus.

  2. Hinweis 2

    config.get("port") liefert bei fehlendem Schlüssel None, und isinstance(None, int) ist False: Eine Prüfung deckt Fehlen und falschen Typ ab.

  3. Hinweis 3

    Schließen Sie bool ausdrücklich aus: not isinstance(port, int) or isinstance(port, bool) or not 1 <= port <= 65535.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import tomllib

ALLOWED = {"name", "port", "debug"}


def load_settings(text: str) -> dict[str, object]:
    """Parse TOML settings and check them; raise ValueError on any problem."""
    config = tomllib.loads(text)  # TOMLDecodeError is a ValueError too
    unknown = set(config) - ALLOWED
    if unknown:
        raise ValueError(f"unknown key: {sorted(unknown)[0]}")
    name = config.get("name")
    if not isinstance(name, str) or not name:
        raise ValueError("name must be a non-empty string")
    port = config.get("port")
    if not isinstance(port, int) or isinstance(port, bool) or not 1 <= port <= 65535:
        raise ValueError("port must be an integer from 1 to 65535")
    debug = config.get("debug", False)
    if not isinstance(debug, bool):
        raise ValueError("debug must be true or false")
    return {"name": name, "port": port, "debug": debug}
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

import tomllib

ALLOWED = {"name", "port", "debug"}


def load_settings(text: str) -> dict[str, object]:
    """Parse TOML settings and check them; raise ValueError on any problem."""
    config = tomllib.loads(text)
    # Check: no key outside ALLOWED; name a non-empty str; port an int
    # (not a bool) from 1 to 65535; debug a bool, False when missing.
    return config

test_main.py

from main import load_settings


def error_of(text):
    try:
        load_settings(text)
    except ValueError as err:
        return str(err)
    return None


def test_valid():
    """Eine vollständige, gültige Konfiguration wird zurückgegeben"""
    got = load_settings('name = "api"\nport = 8080\ndebug = true\n')
    assert got == {"name": "api", "port": 8080, "debug": True}, f"load_settings lieferte {got!r}"


def test_debug_default():
    """debug ist ohne Angabe False"""
    got = load_settings('name = "api"\nport = 8080\n')
    assert got == {"name": "api", "port": 8080, "debug": False}, f"load_settings lieferte {got!r}"


def test_missing_name():
    """Ein fehlender name löst ValueError aus, der den Schlüssel nennt"""
    got = error_of("port = 8080\n")
    assert got is not None and "name" in got, f"erwartet ValueError mit name in der Meldung, erhalten {got!r}"


def test_port_bool():
    """port = true wird abgelehnt, obwohl True ein int ist"""
    got = error_of('name = "api"\nport = true\n')
    assert got is not None and "port" in got, f"erwartet ValueError mit port in der Meldung, erhalten {got!r}"


def test_port_text_and_range():
    """port = "80" und port = 70000 werden abgelehnt"""
    got = error_of('name = "api"\nport = "80"\n'), error_of('name = "api"\nport = 70000\n')
    assert None not in got, f"erwartet zwei ValueErrors, erhalten {got!r}"


def test_unknown_key():
    """Ein vertippter Schlüssel wie prot wird gemeldet"""
    got = error_of('name = "api"\nport = 8080\nprot = 1\n')
    assert got is not None and "prot" in got, f"erwartet ValueError mit prot in der Meldung, erhalten {got!r}"


def test_bad_toml():
    """Ungültiges TOML löst ebenfalls ValueError aus"""
    got = error_of("name = api\n")
    assert got is not None, "ungültiges TOML hat keinen ValueError ausgelöst"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Die Datei im Textmodus öffnen

import tomllib

with open("app.toml", "w", encoding="utf-8") as f:
    f.write('name = "demo"\n')

with open("app.toml", encoding="utf-8") as f:
    config = tomllib.load(f)

Was Python ausgibt

TypeError: File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`

Warum, und die Lösung

tomllib.load liest Bytes und dekodiert sie selbst als UTF-8, denn TOML-Dateien sind immer UTF-8. Öffnen Sie die Datei mit open(path, "rb") und lassen Sie encoding= weg. Haben Sie den Text schon als str, rufen Sie stattdessen tomllib.loads(text) auf.

bytes an loads übergeben

import tomllib

data = b'name = "demo"'
config = tomllib.loads(data)

Was Python ausgibt

TypeError: Expected str object, not 'bytes'

Warum, und die Lösung

loads nimmt einen str. Bytes, etwa aus dem Netz oder einer ZIP-Datei, dekodieren Sie zuerst, tomllib.loads(data.decode("utf-8")), oder Sie verpacken sie für load: tomllib.load(io.BytesIO(data)).

Ein String ohne Anführungszeichen

import tomllib

config = tomllib.loads("name = unquoted")

Was Python ausgibt

tomllib.TOMLDecodeError: Invalid value (at line 1, column 8)

Warum, und die Lösung

In TOML braucht jeder String Anführungszeichen: name = "unquoted". Nur Zahlen, true, false, Datumsangaben, Arrays und Tabellen stehen ohne. Zeile und Spalte in der Meldung zeigen auf das erste Zeichen, das der Parser nicht lesen konnte.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

loads für Text, load für eine Binärdatei

tomllib.loads(text) parst einen str; tomllib.load(f) parst ein Dateiobjekt, das im Binärmodus geöffnet sein muss, open(path, "rb"), denn TOML ist immer UTF-8 und der Parser dekodiert selbst. Beide liefern ein gewöhnliches dict. Eine Datei im Textmodus löst TypeError aus, ebenso bytes an loads. Ungültiges TOML löst tomllib.TOMLDecodeError aus, eine Unterklasse von ValueError, seit 3.14 mit msg, lineno und colno. Das Modul liest nur: Zum Schreiben brauchen Sie ein Paket von Drittanbietern.

Wie TOML-Typen in Python ankommen

Eine TOML-Tabelle, [database] oder {host = "x"}, wird ein dict, [server.tls] ein dict in einem dict. Ein Array wird eine Liste, ein Array von Tabellen, [[servers]], eine Liste von dicts. Ganzzahlen, Kommazahlen, Wahrheitswerte und Strings werden int, float, bool und str. Datum und Uhrzeit kommen als Objekte, nicht als Text: 2026-09-29 ist ein datetime.date, 07:30:00 ein datetime.time, ein Zeitpunkt mit Z oder Offset ein datetime mit Zeitzone. parse_float=Decimal liefert exakte Dezimalzahlen, denn der Parser übergibt ihm den Text jeder Kommazahl.

Parsen ist nicht Prüfen

tomllib prüft die Syntax, nicht die Bedeutung: Ein fehlender Schlüssel, port = "80" oder ein vertipptes prot werden anstandslos geparst. Prüfen Sie direkt nach dem Laden und lösen Sie ValueError mit dem Schlüssel in der Meldung aus, damit der Fehler auf die Datei zeigt und nicht auf einen KeyError tief im Programm. Vorsicht bei bool: True ist ein int, isinstance(port, int) akzeptiert also port = true; ergänzen Sie not isinstance(port, bool). Standardwerte mischen Sie mit DEFAULTS | loaded, rechts gewinnt. Bei fremden Eingaben warnt die Doku vor hohem CPU- und Speicherverbrauch: Begrenzen Sie vorher die Größe.

Quellen

Zuletzt geprüft am 29. September 2026