using flattening

This commit is contained in:
2026-05-21 16:48:46 +02:00
parent d2000dc040
commit 807d07f28e
2 changed files with 596 additions and 577 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,252 @@
# %%
import dataclasses as dc
import datetime
import enum
import json
import re
from collections.abc import Sequence
from pprint import pprint
from typing import Any
import babel
from pydantic import BaseModel
from pydantic import BaseModel, model_validator
from PySide6.QtCore import QDate, Qt
# %%
gui_rohdaten = {
"projekt_name": "Mars Rover",
"meilensteine": [
{"titel": "Triebwerk Test", "finanzen__betrag": 5000.0, "finanzen__waehrung": "EUR"},
{"titel": "Software Beta", "finanzen__betrag": 1200.0, "finanzen__waehrung": "EUR"},
],
}
class FlatBaseModel(BaseModel):
@classmethod
def _unflatten_dict(cls, flat_dict: dict) -> dict:
"""Hilfsmethode: Macht aus {'a__b': 1} wieder {'a': {'b': 1}}"""
result = {}
for key, value in flat_dict.items():
if "__" in key:
parts = key.split("__")
aktuell = result
for part in parts[:-1]:
if part not in aktuell or not isinstance(aktuell[part], dict):
aktuell[part] = {}
aktuell = aktuell[part]
aktuell[parts[-1]] = value
else:
result[key] = value
return result
@model_validator(mode="before")
@classmethod
def __unflatten_input(cls, data: Any) -> Any:
"""Eingangskontrolle: Verarbeitet flache DB/GUI-Daten zu Pydantic-Strukturen."""
if not isinstance(data, dict):
return data
# 1. Haupt-Struktur wiederherstellen
unflattened = cls._unflatten_dict(data)
pprint(unflattened)
# 2. Schleife über die Felder, um JSON-Listen von Sub-Modellen zu entpacken
for key, value in unflattened.items():
if isinstance(value, str) and value.startswith("[") and value.endswith("]"):
try:
parsed = json.loads(value)
if isinstance(parsed, list):
# Falls die Liste Dictionaries enthält, ent-flachen wir diese einzeln
unflattened[key] = [
cls._unflatten_dict(item) if isinstance(item, dict) else item
for item in parsed
]
else:
unflattened[key] = parsed
except json.JSONDecodeError:
pass
if isinstance(value, (list)):
unflattened[key] = [
cls._unflatten_dict(item) if isinstance(item, dict) else item
for item in value
]
pprint(unflattened)
return unflattened
def to_db(self) -> dict[str, Any]:
"""Ausgang für die DB: Flach, Listen sind JSON-Strings."""
nested = super().model_dump()
return self.__flatten_dict(nested, serialize_lists=True)
def to_gui(self) -> dict[str, Any]:
"""Ausgang für die GUI: Flach, aber Listen bleiben Python-Listen für Widgets."""
nested = super().model_dump()
return self.__flatten_dict(nested, serialize_lists=False)
@classmethod
def __flatten_dict(
cls, nested_dict: dict, parent_key: str = "", serialize_lists: bool = True
) -> dict:
"""Rekursiver Alleskönner zum Abflachen von Strukturen."""
items = []
for k, v in nested_dict.items():
new_key = f"{parent_key}__{k}" if parent_key else k
if isinstance(v, dict):
items.extend(cls.__flatten_dict(v, new_key, serialize_lists).items())
elif isinstance(v, list):
processed_list = []
for item in v:
if isinstance(item, dict):
# WICHTIG: Das Dict in der Liste wird isoliert abgeflacht (ohne parent_key),
# da es ein eigenständiges Zeilen-Objekt im DynamicListWidget bleibt!
processed_list.append(
cls.__flatten_dict(item, serialize_lists=serialize_lists)
)
else:
processed_list.append(item)
if serialize_lists:
items.append((new_key, json.dumps(processed_list)))
else:
items.append((new_key, processed_list))
else:
items.append((new_key, v))
return dict(items)
# %%
class BudgetDetails(BaseModel):
betrag: float
waehrung: str = "EUR"
class Meilenstein(BaseModel):
titel: str
finanzen: BudgetDetails # <--- Verschachtelung innerhalb der Liste!
class ProjektModell(FlatBaseModel):
projekt_name: str
meilensteine: list[Meilenstein] # <--- Die Liste von Modellen
projekt = ProjektModell(**gui_rohdaten)
# %%
projekt.meilensteine
# %%
target_dict = {
"t1": "test",
"t2": "test2",
"t3": ["t3-1", "t3-2", "t3-3"],
"t4": {
"t4-1": "t4-1",
"t4-2": "t4-2",
},
"t5": [
{"sub1": "sub1-1"},
{"sub2": "sub2-1"},
{"sub3": "sub3-1"},
],
}
target_dict = {
"Stammdaten": {
"Stammdaten_PLZ": "4523",
"Stammdaten_anrede_anschrift": "Sehr geehrter Herr",
"Stammdaten_anzahl_kinder": {"alter": [3, 7, 10, 14], "anzahl": 4},
"Stammdaten_aufenthaltsort": "Ausland EU/EWR",
"Stammdaten_bundesland": None,
"Stammdaten_email": "max.mustermann@test.at",
"Stammdaten_familienstand": "ledig",
"Stammdaten_festnetznummer": "123456789",
"Stammdaten_geburtsdatum": datetime.date(1990, 4, 11),
"Stammdaten_hausnummer": "12",
"Stammdaten_herkunftsland": "AT",
"Stammdaten_land": None,
"Stammdaten_mobilfunknummer": "123456789",
"Stammdaten_name": "Mustermann",
"Stammdaten_ort": "Ort in Österreich",
"Stammdaten_rueckkehrer": False,
"Stammdaten_staatsangehoerigkeit": "AT",
"Stammdaten_strasse": "Teststraße",
"Stammdaten_titel": "Test",
"Stammdaten_vorname": "Max",
},
"Schulbildung": [
{"Schulbildung-[1]__SB_abschluss": None, "Schulbildung-[1]__SB_abschlussgrad": None},
{"Schulbildung-[2]__SB_abschluss": None, "Schulbildung-[2]__SB_abschlussgrad": None},
],
}
def flatten_for_db(
nested_dict: dict,
parent_key: str = "",
sep: str = ".",
) -> dict[str, Any]:
items = []
for k, v in nested_dict.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_for_db(v, new_key, sep=sep).items())
elif isinstance(v, list):
items.append((new_key, json.dumps(v)))
else:
items.append((new_key, v))
return dict(items)
def unflatten_from_db(
flat_dict: dict,
sep: str = ".",
) -> dict[str, Any]:
result = {}
for key, value in flat_dict.items():
if isinstance(value, str) and (value.startswith("[") and value.endswith("]")):
try:
value = json.loads(value)
except json.JSONDecodeError:
pass
parts = key.split(sep)
aktuell = result
for part in parts[:-1]:
if part not in aktuell:
aktuell[part] = {}
aktuell = aktuell[part]
aktuell[parts[-1]] = value
return result
flatted = flatten_for_db(target_dict, sep="__")
pprint(flatted)
unflatted = unflatten_from_db(flatted, sep="__")
print("\n\n")
pprint(unflatted)
# %%
flatted
# %%
string = """
Metallerzeugung & -bearbeitung; Elektro, Energie, Chemie; IT & Software; Kunststoff, Papier, Textil; Logistik, Verkehr, Transport; Handwerk, Bau, Grüne Berufe; Gesundheit & Pflege; Tourismus & Gastronomie; Handel; Bildung & Soziales; Entwicklung, Planung, Qualität; Administration, Finanzen, Verwaltung; Marketing, Design, Vertrieb; Einkauf, Lager, Wartung; Sonstige; Keine Schwerpunkte, branchenübergreifende Rekrutierung
"""
parts = string.strip().split(";")
[part.strip() for part in parts]
# %%
DYNAMIC_LIST_KEY_PATTERN = re.compile(r"-\[(\d+)\]")
@@ -153,9 +391,6 @@ def get_country_list_german() -> CountryList:
)
# %%
laender_liste
# %%
DYNAMIC_LIST_KEY_PATTERN = r"-\[(\d+)\]"
key = "Schulbildung-[12].7b8da0f7-7a0e-4f71-878a-85616099e849"