Compare commits
No commits in common. "master" and "klassen2" have entirely different histories.
149
orders.log
149
orders.log
File diff suppressed because one or more lines are too long
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
@ -1,49 +0,0 @@
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(f"orders.{__name__}") # → "orders.load_files"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# JSON-File einlesen
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def load_orders(path: str | Path) -> 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:
|
||||
logger.warning("Datei nicht gefunden: %s", path)
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Konnte Datei {path} nicht decodieren")
|
||||
return None
|
||||
except UnicodeDecodeError as e:
|
||||
logger.warning(f"Datei {path} scheint ein falsches Coding zu haben")
|
||||
logger.debug(
|
||||
f"UnicodeDecodeError-Details: "
|
||||
f"encoding={e.encoding}, "
|
||||
f"reason={e.reason}, "
|
||||
f"start={e.start}, "
|
||||
f"end={e.end}, "
|
||||
f"bad_bytes={hex(e.object[e.start])}"
|
||||
)
|
||||
return None
|
||||
@ -1,46 +0,0 @@
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from utils import setup_logger_extended
|
||||
from load_files import load_orders
|
||||
from validation import process_orders
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logger = setup_logger_extended("orders")
|
||||
# logger = setup_logger()
|
||||
|
||||
files = [
|
||||
"orders_1_valid.json",
|
||||
"orders_5_non_existing_file.json",
|
||||
"orders_2_parse_error.json",
|
||||
"orders_3_encoding_error.json",
|
||||
"orders_4_invalid_order.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)
|
||||
|
||||
if orders is not None:
|
||||
process_orders(orders)
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Alle {len(files)} Dateien verarbeitet. Details siehe orders.log")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,45 +0,0 @@
|
||||
import sys
|
||||
import logging
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Logging-Konfiguration
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
@ -1,115 +0,0 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(f"orders.{__name__}") # → "orders.validation"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# 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>")
|
||||
|
||||
for item in order.get("items", []):
|
||||
qty = item.get("qty")
|
||||
if qty is not None and qty < 0:
|
||||
raise InvalidOrderError(
|
||||
order_id=order_id,
|
||||
field="qty",
|
||||
value=qty,
|
||||
message=f"Negative Menge ({qty}) ist nicht erlaubt.",
|
||||
)
|
||||
|
||||
total = order.get("total_chf")
|
||||
if total is not None and total < 0:
|
||||
raise InvalidOrderError(
|
||||
order_id=order_id,
|
||||
field="total_chf",
|
||||
value=total,
|
||||
message=f"Negativer Gesamtbetrag ({total} CHF) ist nicht erlaubt.",
|
||||
)
|
||||
|
||||
if not order.get("order_id"):
|
||||
raise InvalidOrderError(
|
||||
order_id="<unbekannt>",
|
||||
field="order_id",
|
||||
value=order.get("order_id"),
|
||||
message="Pflichtfeld 'order_id' fehlt oder ist leer.",
|
||||
)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Validierungs-Durchlauf über alle Bestellungen
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def process_orders(orders: list[dict]) -> 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.debug("OK: %s", order.get("order_id"))
|
||||
|
||||
except InvalidOrderError as e:
|
||||
invalid_count += 1
|
||||
logger.warning(
|
||||
f"Ungültige Bestellung — order_id={e.order_id}, feld={e.field}, wert={e.value}: {e.message}"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Validierung abgeschlossen: {valid_count} gültig, {invalid_count} ungültig."
|
||||
)
|
||||
@ -1,5 +0,0 @@
|
||||
def discount_price(price: float, percent: float) -> float:
|
||||
return price - price * percent / 100
|
||||
|
||||
|
||||
print(discount_price(100.0, 20.0))
|
||||
@ -1,6 +0,0 @@
|
||||
from src.u6_tests.pricing import discount_price
|
||||
|
||||
|
||||
def test_discount_price_reduces_price():
|
||||
result = discount_price(100.0, 20.0)
|
||||
assert result == 80.0
|
||||
Loading…
x
Reference in New Issue
Block a user