process orders with error handling

This commit is contained in:
Michael Schären 2026-03-22 19:23:20 +01:00
parent ecf4a2ed7a
commit 327e9497b6
6 changed files with 17151 additions and 0 deletions

View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

228
src/order5_initial/main.py Normal file
View File

@ -0,0 +1,228 @@
"""
main.py Bestellungen einlesen und validieren
Demonstriert:
- Standard-Logging (logging-Modul) mit FileHandler + StreamHandler
- Sauberes Exception-Handling für UnicodeDecodeError & json.JSONDecodeError
- Eigene Exception-Klasse InvalidOrderError (erbt von ValueError)
"""
import json
import sys
from pathlib import Path
import logging
# ══════════════════════════════════════════════════════════════════════════════
# Logging-Konfiguration
# ══════════════════════════════════════════════════════════════════════════════
def setup_logger(name: str = None, log_file: str = "orders.log") -> logging.Logger:
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(name)s %(message)s",
)
logger = logging.getLogger(__name__)
return logger
LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)-20s | %(message)s"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
def setup_logger_extended(name: str, log_file: str = "orders.log") -> logging.Logger:
"""
Erstellt und konfiguriert einen Logger mit zwei Handlern:
- StreamHandler Ausgabe auf die Konsole (ab INFO)
- FileHandler Ausgabe in eine Log-Datei (ab DEBUG)
"""
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG) # Root-Level: alles durchlassen
# Konsole: INFO und höher
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.INFO)
stream_handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT))
# Log-File: DEBUG und höher (detaillierter)
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT))
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
return logger
# ══════════════════════════════════════════════════════════════════════════════
# JSON-File einlesen
# ══════════════════════════════════════════════════════════════════════════════
def load_orders(path: str | Path, logger: logging.Logger) -> list[dict] | None:
"""
Liest eine JSON-Datei mit Bestellungen ein.
Behandelte Fehler
-----------------
UnicodeDecodeError Falsche Kodierung (z. B. Latin-1 statt UTF-8)
json.JSONDecodeError Ungültiges JSON (Syntaxfehler)
Rückgabe
--------
Liste der Bestellungen bei Erfolg, None bei Fehler.
"""
path = Path(path)
logger.info("Lese Datei: %s", path)
try:
with path.open("r", encoding="utf-8") as f:
loaded_file = json.load(f)
logger.info(f"{path} erfolgreich eingelesen")
return loaded_file
except (
FileNotFoundError,
PermissionError,
json.JSONDecodeError,
UnicodeDecodeError,
) as ex:
# logger.error(f"Fehler beim Lesen von File: %s", path)
logger.error(f"{ex.__class__.__name__}: {str(ex)}")
return None
# ══════════════════════════════════════════════════════════════════════════════
# Eigene Exception-Klasse
# ══════════════════════════════════════════════════════════════════════════════
class InvalidOrderError(ValueError):
"""
Wird ausgelöst, wenn eine Bestellung ungültige Geschäftsdaten enthält.
Erbt von ValueError, weil es sich um einen inhaltlichen Wertfehler handelt
(kein technisches I/O-Problem).
Attribute
---------
order_id : str
Die ID der fehlerhaften Bestellung.
field : str
Der Name des ungültigen Feldes (z. B. "qty").
value : object
Der tatsächliche (ungültige) Wert.
message : str
Lesbare Fehlerbeschreibung (auch als str(e) verfügbar).
"""
def __init__(self, order_id: str, field: str, value: object, message: str):
self.order_id = order_id
self.field = field
self.value = value
self.message = message
super().__init__(message)
def __str__(self) -> str:
return (
f"InvalidOrderError | order_id={self.order_id!r} "
f"| field={self.field!r} | value={self.value!r} "
f"| {self.message}"
)
# ══════════════════════════════════════════════════════════════════════════════
# Geschäftslogik: Validierung einer einzelnen Bestellung
# ══════════════════════════════════════════════════════════════════════════════
def validate_order(order: dict) -> None:
"""
Prüft eine einzelne Bestellung auf Plausibilität.
Wirft InvalidOrderError, sobald ein Regelverstoß entdeckt wird.
Regeln (erweiterbar):
- qty darf nicht negativ sein
- total_chf darf nicht negativ sein
- order_id und status müssen vorhanden sein
"""
order_id = order.get("order_id", "<unbekannt>")
if order_id == "<unbekannt>":
raise InvalidOrderError(
order_id, "order_id", order_id, "order_id muss vorhanden sein"
)
order_state = order.get("status", "<unbekannt>")
if not order_state:
raise InvalidOrderError(
order_state, "order_state", order_state, "order_state muss vorhanden sein"
)
# loop über alle items in einem order -> falls Kontrolle (Regeln!) nicht eingehalten werden -> raise InvalidOrderError()
for item in order.get("items", []):
qty = item.get("qty")
if not qty or qty < 1:
raise InvalidOrderError(order_id, "qty", qty, "qty darf nicht negativ sein")
total_chf = order.get("total_chf")
if not total_chf or total_chf < 0:
raise InvalidOrderError(
order_id, "total_chf", total_chf, "total_chf darf nicht negativ sein"
)
# ══════════════════════════════════════════════════════════════════════════════
# Validierungs-Durchlauf über alle Bestellungen
# ══════════════════════════════════════════════════════════════════════════════
def process_orders(orders: list[dict], logger: logging.Logger) -> None:
"""
Iteriert über alle Bestellungen und validiert jede einzelne.
Ungültige Bestellungen werden geloggt und übersprungen (kein Abbruch).
"""
valid_count = 0
invalid_count = 0
for order in orders.get("orders", []):
try:
validate_order(order)
valid_count += 1
logger.info("OK: %s", order.get("order_id"))
except InvalidOrderError as ex:
invalid_count += 1
logger.error("NOK: %s", str(ex))
def main() -> None:
logger = setup_logger_extended(name="order")
# logger = setup_logger()
files = [
"orders_1.json",
"orders_5.json",
"orders_2.json",
"orders_3.json",
"orders_4.json",
]
for filename in files:
BASE_DIR = Path(__file__).parent
file_path = BASE_DIR / "data" / filename
logger.info("=" * 60)
logger.info("Verarbeite: %s", filename)
orders = load_orders(file_path, logger)
if orders is not None:
process_orders(orders, logger)
logger.info("=" * 60)
logger.info(f"Alle {len(files)} Dateien verarbeitet. Details siehe orders.log")
if __name__ == "__main__":
main()