generated from dopt-python/py311
using flattening
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,252 @@
|
|||||||
# %%
|
# %%
|
||||||
import dataclasses as dc
|
import dataclasses as dc
|
||||||
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
from pprint import pprint
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import babel
|
import babel
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, model_validator
|
||||||
from PySide6.QtCore import QDate, Qt
|
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+)\]")
|
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+)\]"
|
DYNAMIC_LIST_KEY_PATTERN = r"-\[(\d+)\]"
|
||||||
key = "Schulbildung-[12].7b8da0f7-7a0e-4f71-878a-85616099e849"
|
key = "Schulbildung-[12].7b8da0f7-7a0e-4f71-878a-85616099e849"
|
||||||
|
|||||||
Reference in New Issue
Block a user