Files
Inventory/scripts/normalize-dump.py
r-dev d65eb9ff0f WIP: corrections sérialisation API et script normalisation SQL
Backend:
- Fix Groups sur TypeMachine*Requirement: exposer typePiece/typeComposant/typeProduct
- Fix Groups sur Document, Piece, Product, Composant pour sérialisation
- Add addConstructeur/removeConstructeur sur Piece et Product

Scripts:
- Fix normalize-dump.py: gérer les schémas quotés ("public"."table")

Frontend (sous-module):
- Corrections formulaires et sérialisation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:34:00 +01:00

178 lines
5.2 KiB
Python

#!/usr/bin/env python3
import re
import sys
INSERT_RE = re.compile(
r"(?P<prefix>INSERT\s+INTO\s+[^;]*?\()(?P<cols>[^)]*)(?P<suffix>\)\s+VALUES)",
re.IGNORECASE | re.DOTALL,
)
TABLE_RE = re.compile(
r"(?P<before>INSERT\s+INTO\s+)(?P<table>(?:\"[^\"]+\"\.|[A-Za-z_][\w$]*\.)?\"[^\"]+\"|(?:\"[^\"]+\"\.|[A-Za-z_][\w$]*\.)?[A-Za-z_][\w$]*)",
re.IGNORECASE,
)
CREATE_DB_RE = re.compile(r"^CREATE\s+DATABASE\s+.+?;$", re.IGNORECASE | re.MULTILINE)
CONNECT_RE = re.compile(r"^\\connect\\s+.+?$", re.IGNORECASE | re.MULTILINE)
TABLE_NAME_MAP = {
"ModelType": "model_types",
"TypeMachine": "type_machines",
"TypeMachineComponentRequirement": "type_machine_component_requirements",
"TypeMachinePieceRequirement": "type_machine_piece_requirements",
"TypeMachineProductRequirement": "type_machine_product_requirements",
"MachinePieceLink": "machine_piece_links",
"MachineComponentLink": "machine_component_links",
"MachineProductLink": "machine_product_links",
"Machine": "machines",
"Product": "products",
"Piece": "pieces",
"Composant": "composants",
"Profile": "profiles",
"CustomField": "custom_fields",
"CustomFieldValue": "custom_field_values",
"Document": "documents",
"Constructeur": "constructeurs",
"Site": "sites",
}
SKIP_TABLES = {
"_prisma_migrations",
}
def to_snake(name: str) -> str:
out = []
length = len(name)
for i, ch in enumerate(name):
if ch.isupper():
prev = name[i - 1] if i > 0 else ""
nxt = name[i + 1] if i + 1 < length else ""
if i > 0 and (prev.islower() or prev.isdigit() or (prev.isupper() and nxt.islower())):
out.append("_")
out.append(ch.lower())
else:
out.append(ch)
return "".join(out)
def to_lower_compact(name: str) -> str:
return name.replace("_", "").lower()
def remap_table(ident: str, mode: str) -> str:
mapped = TABLE_NAME_MAP.get(ident)
if mapped is not None:
return mapped
if mode == "snake":
return to_snake(ident)
if mode == "lower":
return to_lower_compact(ident)
raise ValueError(f"Unsupported mode: {mode}")
def extract_table_ident(prefix: str) -> str | None:
match = TABLE_RE.search(prefix)
if not match:
return None
table = match.group("table")
# Handle quoted schema like "public"."TableName"
if '"."' in table:
parts = table.split('"."', 1)
ident = parts[1].strip('"')
elif "." in table:
_, ident = table.split(".", 1)
ident = ident.strip('"')
else:
ident = table.strip('"')
return ident
def normalize_table_name(prefix: str, mode: str) -> str:
def repl(match: re.Match[str]) -> str:
table = match.group("table")
schema = ""
ident = table
# Handle quoted schema like "public"."TableName"
if '"."' in table:
parts = table.split('"."', 1)
schema_name = parts[0].strip('"')
ident = parts[1].strip('"')
schema = f'"{schema_name}".'
elif "." in table:
schema_part, ident = table.split(".", 1)
schema_name = schema_part.strip('"')
schema = f'"{schema_name}".'
ident = ident.strip('"')
else:
ident = table.strip('"')
mapped = remap_table(ident, mode)
return f'{match.group("before")}{schema}"{mapped}"'
return TABLE_RE.sub(repl, prefix)
def remap_columns(cols: str, mode: str) -> str:
def repl(match: re.Match[str]) -> str:
name = match.group(1)
if mode == "snake":
if any(ch.isupper() for ch in name):
return f"\"{to_snake(name)}\""
return match.group(0)
if mode == "lower":
return f"\"{to_lower_compact(name)}\""
raise ValueError(f"Unsupported mode: {mode}")
return re.sub(r"\"([^\"]+)\"", repl, cols)
def normalize_dump(sql: str, mode: str) -> str:
sql = CREATE_DB_RE.sub("", sql)
sql = CONNECT_RE.sub("", sql)
def repl(match: re.Match[str]) -> str:
raw_prefix = match.group("prefix")
ident = extract_table_ident(raw_prefix)
if ident is not None:
mapped = remap_table(ident, mode)
if ident in SKIP_TABLES or mapped in SKIP_TABLES:
return ""
prefix = normalize_table_name(raw_prefix, mode)
cols = remap_columns(match.group("cols"), mode)
return f"{prefix}{cols}{match.group('suffix')}"
return INSERT_RE.sub(repl, sql)
def main() -> int:
if len(sys.argv) not in (3, 4):
print("Usage: scripts/normalize-dump.py <input.sql> <output.sql> [--snake|--lower]", file=sys.stderr)
return 1
src = sys.argv[1]
dst = sys.argv[2]
mode = "lower"
if len(sys.argv) == 4:
if sys.argv[3] == "--snake":
mode = "snake"
elif sys.argv[3] == "--lower":
mode = "lower"
else:
print("Invalid mode. Use --snake or --lower.", file=sys.stderr)
return 1
with open(src, "r", encoding="utf-8") as f:
data = f.read()
normalized = normalize_dump(data, mode)
with open(dst, "w", encoding="utf-8") as f:
f.write(normalized)
return 0
if __name__ == "__main__":
raise SystemExit(main())