Endelig
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -32,3 +32,7 @@ output/
|
||||
# Windows
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# tests
|
||||
tests/
|
||||
|
||||
|
||||
2
testenv.env
Normal file
2
testenv.env
Normal file
@@ -0,0 +1,2 @@
|
||||
env=dev
|
||||
edw_hk_plan_ts=20260404
|
||||
@@ -32,7 +32,7 @@ def _byg_argument_parser() -> argparse.ArgumentParser:
|
||||
|
||||
def _kør_udtræk(config: dict, global_config: dict) -> None:
|
||||
"""Kører den normale udtræks- og transformationspipeline."""
|
||||
|
||||
print(f"DEBUG: Antal output_filer = {len(config.get('output_filer', []))}")
|
||||
input_fil = global_config.get("input_fil")
|
||||
input_fil_liste = global_config.get("input_fil_liste")
|
||||
|
||||
@@ -120,9 +120,14 @@ def main():
|
||||
opsaet_logging(
|
||||
log_fil=global_config["logfil"],
|
||||
niveau=global_config.get("log_niveau", "info"),
|
||||
log_output=global_config.get("log_output", "begge"),
|
||||
)
|
||||
|
||||
# Eksekver DDL-flowet
|
||||
print(f"MAIN DEBUG: output_filer count = {len(config.get('output_filer', []))}", flush=True)
|
||||
for i, cfg in enumerate(config.get("output_filer", [])):
|
||||
print(f"MAIN DEBUG [{i}]: rod={cfg.get('rod')}, fil={cfg.get('fil_navn')}, type={cfg.get('type')}", flush=True)
|
||||
|
||||
if ddl.is_enabled(args):
|
||||
ddl.run_ddl_mode(args, config, global_config)
|
||||
else:
|
||||
|
||||
@@ -354,13 +354,16 @@ def valider_yaml(yaml_file_path: str) -> dict:
|
||||
global_config.setdefault("stop_ved_0_output", False)
|
||||
global_config.setdefault("db_char_set", "latin-1")
|
||||
|
||||
# 14) Logfilnavn formateres med dato
|
||||
global_config["logfil"] = str(global_config["logfil"]).format(
|
||||
# 14) Logfilnavn formateres med dato og placeres i output_path
|
||||
global_config["logfil"] = os.path.join(
|
||||
global_config["output_path"],
|
||||
str(global_config["logfil"]).format(
|
||||
yy=global_config["dato"].strftime("%y"),
|
||||
yyyy=global_config["dato"].strftime("%Y"),
|
||||
mm=global_config["dato"].strftime("%m"),
|
||||
dd=global_config["dato"].strftime("%d"),
|
||||
)
|
||||
)
|
||||
|
||||
# 15) Timestamp
|
||||
global_config["var_timestamp"] = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")
|
||||
|
||||
@@ -278,6 +278,10 @@ def generate_insert_move_sql_short(table_name_from_yaml: str, columns: List[str]
|
||||
def run_ddl_mode(args, config: Dict[str, Any], global_config: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Executes the full DDL-only flow and writes files.
|
||||
|
||||
--tmp flaget styrer om der genereres én eller to tabeller:
|
||||
- Uden --tmp: kun den tabel der er nævnt i YAML
|
||||
- Med --tmp: både den nævnte tabel og dens modpart (base↔tmp)
|
||||
"""
|
||||
|
||||
outdir = os.path.join(global_config["output_path"], "sql")
|
||||
@@ -296,78 +300,72 @@ def run_ddl_mode(args, config: Dict[str, Any], global_config: Dict[str, Any]) ->
|
||||
try:
|
||||
base_tabel, tmp_tabel = split_base_tmp(tabel)
|
||||
yaml_is_tmp = tabel.lower().endswith("_tmp")
|
||||
skal_lave_tmp = bool(getattr(args, "tmp", False))
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 1) Base DDL (always)
|
||||
# 1) Primær DDL – brug tabelnavnet præcis som det er i YAML
|
||||
# ---------------------------------------------------------
|
||||
base_conf = deepcopy(file_conf)
|
||||
base_conf["tabel_navn"] = base_tabel
|
||||
primær_conf = deepcopy(file_conf)
|
||||
ddl_sql_primær = generate_create_table_sql(primær_conf, global_config)
|
||||
samlet_sql_indhold.append(f"-- Tabel: {tabel}\n{ddl_sql_primær}\n")
|
||||
|
||||
ddl_sql_base = generate_create_table_sql(base_conf, global_config)
|
||||
samlet_sql_indhold.append(f"-- Tabel: {base_tabel}\n{ddl_sql_base}\n")
|
||||
ddl_primær_name = file_conf.get("ddl_fil_navn") or _default_ddl_filename(tabel)
|
||||
ddl_primær_name = generer_filnavn(ddl_primær_name, global_config)
|
||||
ddl_primær_path = os.path.join(outdir, ddl_primær_name)
|
||||
|
||||
# If YAML already is base, respect ddl_fil_navn if present.
|
||||
# If YAML is tmp (base differs), don't reuse ddl_fil_navn blindly.
|
||||
if (not yaml_is_tmp) and file_conf.get("ddl_fil_navn"):
|
||||
ddl_base_name = file_conf["ddl_fil_navn"]
|
||||
else:
|
||||
ddl_base_name = _default_ddl_filename(base_tabel)
|
||||
with open(ddl_primær_path, "w", encoding="utf-8") as f:
|
||||
f.write(ddl_sql_primær)
|
||||
|
||||
ddl_base_name = generer_filnavn(ddl_base_name, global_config)
|
||||
ddl_base_path = os.path.join(outdir, ddl_base_name)
|
||||
|
||||
with open(ddl_base_path, "w", encoding="utf-8") as f:
|
||||
f.write(ddl_sql_base)
|
||||
|
||||
logger.info(f"[DDL] Skrev {ddl_base_path}")
|
||||
logger.info(f"[DDL] Skrev {ddl_primær_path}")
|
||||
antal += 1
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 2) TMP DDL
|
||||
# - If YAML is _tmp: ALWAYS generate tmp too
|
||||
# - Else: only when --tmp is set
|
||||
# 2) Sekundær DDL – kun hvis --tmp er givet
|
||||
# YAML er base → sekundær er tmp
|
||||
# YAML er _tmp → sekundær er base
|
||||
# ---------------------------------------------------------
|
||||
skal_lave_tmp = yaml_is_tmp or bool(getattr(args, "tmp", False))
|
||||
|
||||
if skal_lave_tmp:
|
||||
tmp_conf = deepcopy(file_conf)
|
||||
tmp_conf["tabel_navn"] = tmp_tabel
|
||||
sekundær_tabel = tmp_tabel if not yaml_is_tmp else base_tabel
|
||||
sekundær_conf = deepcopy(file_conf)
|
||||
sekundær_conf["tabel_navn"] = sekundær_tabel
|
||||
|
||||
ddl_sql_tmp = generate_create_table_sql(tmp_conf, global_config)
|
||||
samlet_sql_indhold.append(f"-- Tabel: {tmp_tabel}\n{ddl_sql_tmp}\n")
|
||||
ddl_sql_sekundær = generate_create_table_sql(sekundær_conf, global_config)
|
||||
samlet_sql_indhold.append(f"-- Tabel: {sekundær_tabel}\n{ddl_sql_sekundær}\n")
|
||||
|
||||
ddl_tmp_name = _default_ddl_filename(tmp_tabel)
|
||||
ddl_tmp_name = generer_filnavn(ddl_tmp_name, global_config)
|
||||
ddl_tmp_path = os.path.join(outdir, ddl_tmp_name)
|
||||
ddl_sekundær_name = generer_filnavn(_default_ddl_filename(sekundær_tabel), global_config)
|
||||
ddl_sekundær_path = os.path.join(outdir, ddl_sekundær_name)
|
||||
|
||||
with open(ddl_tmp_path, "w", encoding="utf-8") as f_tmp:
|
||||
f_tmp.write(ddl_sql_tmp)
|
||||
with open(ddl_sekundær_path, "w", encoding="utf-8") as f:
|
||||
f.write(ddl_sql_sekundær)
|
||||
|
||||
logger.info(f"[DDL] Skrev {ddl_tmp_path}")
|
||||
logger.info(f"[DDL] Skrev {ddl_sekundær_path}")
|
||||
antal += 1
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 3) Flyt scripts (Sybase ASE) if --flyt
|
||||
# Always based on YAML table name, which determines base/tmp
|
||||
# 3) Flyt scripts – kun hvis --flyt er givet
|
||||
# ---------------------------------------------------------
|
||||
if getattr(args, "flyt", False):
|
||||
_skriv_flyt_scripts(tabel, base_tabel, tmp_tabel, file_conf, outdir,
|
||||
generate_insert_move_sql, samlet_flyt_indhold)
|
||||
_skriv_flyt_scripts(
|
||||
tabel, base_tabel, tmp_tabel, file_conf, outdir,
|
||||
generate_insert_move_sql, samlet_flyt_indhold
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4) Flyt scripts (Sybase ASE) if --flyt_kort
|
||||
# Always based on YAML table name, which determines base/tmp
|
||||
# 4) Flyt scripts kort – kun hvis --flyt_kort er givet
|
||||
# ---------------------------------------------------------
|
||||
if getattr(args, "flyt_kort", False):
|
||||
_skriv_flyt_scripts(tabel, base_tabel, tmp_tabel, file_conf, outdir,
|
||||
generate_insert_move_sql_short, samlet_flyt_indhold)
|
||||
|
||||
_skriv_flyt_scripts(
|
||||
tabel, base_tabel, tmp_tabel, file_conf, outdir,
|
||||
generate_insert_move_sql_short, samlet_flyt_indhold
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[DDL] Fejl for {tabel}: {e}")
|
||||
raise
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Combined files
|
||||
# Samlede filer
|
||||
# ---------------------------------------------------------
|
||||
if antal > 0:
|
||||
samlet_sti = os.path.join(outdir, "sql_samlet.sql")
|
||||
@@ -375,7 +373,7 @@ def run_ddl_mode(args, config: Dict[str, Any], global_config: Dict[str, Any]) ->
|
||||
f_alt.write("\n".join(samlet_sql_indhold))
|
||||
logger.info(f"[DDL] Skrev samlet fil: {samlet_sti}")
|
||||
|
||||
if (getattr(args, "flyt", False) or getattr(args, "flyt_kort", False)) and len(samlet_flyt_indhold) > 0:
|
||||
if (getattr(args, "flyt", False) or getattr(args, "flyt_kort", False)) and samlet_flyt_indhold:
|
||||
samlet_flyt_sti = os.path.join(outdir, "sql_flyt_samlet.sql")
|
||||
with open(samlet_flyt_sti, "w", encoding="utf-8") as f_flyt_alt:
|
||||
f_flyt_alt.write("\n".join(samlet_flyt_indhold))
|
||||
|
||||
@@ -8,6 +8,7 @@ from udpak_semistruktur.extract.traversal import (
|
||||
hent_objekt_fra_sti,
|
||||
_resolve_with_indices,
|
||||
matcher_hvis_findes,
|
||||
_extract_text_node,
|
||||
)
|
||||
|
||||
logger = hent_logger(__name__)
|
||||
@@ -33,7 +34,7 @@ def udvid_rod_alternativer(rod: str) -> list[dict]:
|
||||
for choice in choices
|
||||
]
|
||||
|
||||
def hent_fra_spec(spec, default_el, json_data, sti_index, global_config) -> tuple[Any, bool]:
|
||||
def hent_fra_spec(spec, default_el, json_data, sti_index, global_config, returner_liste=False) -> tuple[Any, bool]:
|
||||
"""Henter en værdi fra json_data baseret på en spec-definition. Returnerer (værdi, mangler)."""
|
||||
|
||||
if spec is None:
|
||||
@@ -63,10 +64,24 @@ def hent_fra_spec(spec, default_el, json_data, sti_index, global_config) -> tupl
|
||||
else:
|
||||
if not isinstance(default_el, (dict, list)):
|
||||
return None, True
|
||||
# base_path er ukendt her; brug tom – sti_index-nøgler vil stadig ramme korrekt,
|
||||
# når felt-stien selv rammer lister (acc akkumuleres fra feltet).
|
||||
|
||||
if returner_liste:
|
||||
# Brug rekursiv_udpakning til at samle alle værdier langs stien
|
||||
# fx felt="linjer.produkt" giver ["Produkt A", "Produkt B"]
|
||||
resultater = []
|
||||
for element, _ in rekursiv_udpakning(default_el, felt):
|
||||
if element not in EMPTY_SENTINELS:
|
||||
resultater.append(_extract_text_node(element))
|
||||
return resultater if resultater else None, False
|
||||
else:
|
||||
kandidat = _resolve_with_indices(default_el, felt, sti_index, base_path="")
|
||||
|
||||
# Hvis kandidaten er en liste og vi ikke ønsker at returnere hele listen,
|
||||
# vælg det aktuelle element via sti_index som normalt.
|
||||
# Hvis returner_liste=True, returneres hele listen uberørt til fx join.
|
||||
if isinstance(kandidat, list) and not returner_liste:
|
||||
kandidat = kandidat[0] if kandidat else None
|
||||
|
||||
missing = kandidat is None and not isinstance(default_el, list)
|
||||
return kandidat, missing
|
||||
|
||||
@@ -80,10 +95,15 @@ def hent_kolonne_værdi_med_fallback(kol: dict, el: Any, json_data: Any, sti_ind
|
||||
værdi = evaluer_værdi_token(raw_value, global_config) if raw_value is not None else None
|
||||
missing = raw_value is None
|
||||
else:
|
||||
returner_liste = bool(kol.get("join", False) or kol.get("flatten", False))
|
||||
print(f"DEBUG kolonne={kolnavn}, join={kol.get('join')}, flatten={kol.get('flatten')}, returner_liste={returner_liste}")
|
||||
primær_spec = {"felt": kol["felt"]}
|
||||
if kol.get("rod"):
|
||||
primær_spec["rod"] = kol["rod"]
|
||||
værdi, missing = hent_fra_spec(primær_spec, el, json_data, sti_index, global_config)
|
||||
værdi, missing = hent_fra_spec(
|
||||
primær_spec, el, json_data, sti_index, global_config,
|
||||
returner_liste=returner_liste
|
||||
)
|
||||
|
||||
# 2) Hvis missing -> prøv missing_fallback
|
||||
if missing:
|
||||
|
||||
@@ -15,7 +15,7 @@ def _extract_text_node(v: Any) -> Any:
|
||||
return v["#text"]
|
||||
return v
|
||||
|
||||
def _resolve_with_indices(obj: Any, path: str, sti_index: dict, base_path: str = "") -> Any:
|
||||
def _resolve_with_indices(obj: Any, path: str, sti_index: dict, base_path: str = "", returner_liste: bool = False) -> Any:
|
||||
"""
|
||||
Følger en dot-sti og bruger sti_index til at vælge elementer
|
||||
hver gang vi rammer en liste. base_path er den allerede-resolverede
|
||||
@@ -60,12 +60,14 @@ def _resolve_with_indices(obj: Any, path: str, sti_index: dict, base_path: str =
|
||||
return None
|
||||
|
||||
# Hvis vi ender på en liste, vælg index én sidste gang
|
||||
if isinstance(cur, list):
|
||||
if isinstance(cur, list) and not returner_liste:
|
||||
idx = sti_index.get(acc, 0)
|
||||
try:
|
||||
cur = cur[idx]
|
||||
except (TypeError, IndexError):
|
||||
return None
|
||||
elif isinstance(cur, list) and returner_liste:
|
||||
return _extract_text_node(cur)
|
||||
|
||||
return _extract_text_node(cur)
|
||||
|
||||
|
||||
@@ -13,23 +13,33 @@ LOG_NIVEAUER = {
|
||||
"critical": logging.CRITICAL,
|
||||
}
|
||||
|
||||
def opsaet_logging(log_fil: str, niveau: str = "info") -> None:
|
||||
def opsaet_logging(log_fil: str, niveau: str = "info", log_output: str = "begge") -> None:
|
||||
"""
|
||||
Opsætter global logging til både fil og konsol.
|
||||
niveau styres fra config (debug/info/warning/error/critical).
|
||||
Opsætter global logging til fil og/eller konsol.
|
||||
log_output styres fra config: 'fil', 'konsol' eller 'begge'.
|
||||
niveau styres fra config: debug/info/warning/error/critical.
|
||||
"""
|
||||
log_niveau = LOG_NIVEAUER.get(niveau.lower(), logging.INFO)
|
||||
|
||||
os.makedirs(os.path.dirname(log_fil), exist_ok=True)
|
||||
handlers = []
|
||||
|
||||
if log_output in ("fil", "begge"):
|
||||
log_mappe = os.path.dirname(log_fil)
|
||||
if log_mappe:
|
||||
os.makedirs(log_mappe, exist_ok=True)
|
||||
handlers.append(logging.FileHandler(log_fil, encoding="utf-8"))
|
||||
|
||||
if log_output in ("konsol", "begge"):
|
||||
handlers.append(logging.StreamHandler())
|
||||
|
||||
if not handlers:
|
||||
handlers.append(logging.StreamHandler()) # fallback så vi aldrig er helt tavse
|
||||
|
||||
logging.basicConfig(
|
||||
level=log_niveau,
|
||||
format=fmt,
|
||||
datefmt=datefmt,
|
||||
handlers=[
|
||||
logging.FileHandler(log_fil, encoding="utf-8"),
|
||||
logging.StreamHandler(),
|
||||
]
|
||||
handlers=handlers,
|
||||
)
|
||||
|
||||
def hent_logger(navn: str) -> logging.Logger:
|
||||
|
||||
@@ -166,7 +166,7 @@ def konverter(data: dict, file_config: dict, global_config: dict) -> dict:
|
||||
tmp = value[:string_truncate]
|
||||
else:
|
||||
tmp = value
|
||||
elif field_type in ["hash", "id", "file", "rod_variant"]:
|
||||
elif field_type in ["hash", "id", "file", "rod_variant", "rod_path"]:
|
||||
tmp = value
|
||||
else:
|
||||
raise ValueError(f"Ukendt datatype '{field_type}' for feltet '{kolonnenavn}'.")
|
||||
|
||||
Reference in New Issue
Block a user