180 lines
5.1 KiB
Python
180 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Import clients depuis data.txt (format psql pipe-séparé) vers PostgreSQL.
|
||
Usage : python3 import_clients.py [--dry-run]
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
|
||
import psycopg2
|
||
|
||
# ── Connexion ──────────────────────────────────────────────────────────────────
|
||
DB_CONFIG = {
|
||
"host": "localhost",
|
||
"port": 5432,
|
||
"user": "",
|
||
"password": "",
|
||
"dbname": "",
|
||
}
|
||
|
||
DATA_FILE = os.path.join(os.path.dirname(__file__), "data.txt")
|
||
DRY_RUN = "--dry-run" in sys.argv
|
||
|
||
# ── Ordre des colonnes dans data.txt (DB production, construite incrémentalement)
|
||
# None = colonne inconnue/obsolète à ignorer
|
||
SOURCE_COLUMNS = [
|
||
"id",
|
||
"username",
|
||
"password",
|
||
"nom",
|
||
"prenom",
|
||
"telephone",
|
||
"command",
|
||
"amende",
|
||
"cancel_commande",
|
||
"referral_balance", # position 9 : NUMERIC(10,2) → "0.00"
|
||
"cancellations_count", # position 10 : INTEGER → "0"
|
||
None, # position 11 : colonne obsolète/inconnue, ignorée
|
||
"last_penalty_reason", # position 12 : TEXT
|
||
"updated_at",
|
||
"created_at",
|
||
"must_change_password", # position 15 : BOOLEAN → "f"/"t"
|
||
]
|
||
|
||
|
||
def parse_value(v: str):
|
||
"""Convertit une valeur brute psql en type Python (None si vide)."""
|
||
v = v.strip()
|
||
if v == "" or v.lower() == "null":
|
||
return None
|
||
if v.lower() == "t":
|
||
return True
|
||
if v.lower() == "f":
|
||
return False
|
||
return v
|
||
|
||
|
||
def load_data(path: str) -> list[list]:
|
||
rows = []
|
||
with open(path, encoding="utf-8") as f:
|
||
for line in f:
|
||
line = line.rstrip("\n")
|
||
if not line.strip():
|
||
continue
|
||
# Lignes séparateur psql (ex: "----+----")
|
||
if (
|
||
set(
|
||
line.replace("|", "")
|
||
.replace("+", "")
|
||
.replace("-", "")
|
||
.replace(" ", "")
|
||
)
|
||
== set()
|
||
):
|
||
continue
|
||
parts = line.split("|")
|
||
values = (
|
||
[parse_value(p) for p in parts[:-1]]
|
||
if parts[-1].strip() == ""
|
||
else [parse_value(p) for p in parts]
|
||
)
|
||
if values:
|
||
rows.append(values)
|
||
return rows
|
||
|
||
|
||
def main():
|
||
rows = load_data(DATA_FILE)
|
||
if not rows:
|
||
print("❌ Aucune ligne trouvée dans data.txt")
|
||
sys.exit(1)
|
||
|
||
print(f"📂 {len(rows)} lignes lues depuis {DATA_FILE}")
|
||
|
||
data_col_count = len(rows[0])
|
||
print(
|
||
f"📋 Colonnes data.txt : {data_col_count} | SOURCE_COLUMNS définis : {len(SOURCE_COLUMNS)}"
|
||
)
|
||
|
||
if data_col_count != len(SOURCE_COLUMNS):
|
||
print(
|
||
f"⚠️ Ajuste SOURCE_COLUMNS dans le script ({len(SOURCE_COLUMNS)} définis, {data_col_count} dans le fichier)"
|
||
)
|
||
sys.exit(1)
|
||
|
||
# Colonnes à insérer (on filtre les None = colonnes ignorées)
|
||
insert_cols = [col for col in SOURCE_COLUMNS if col is not None and col != "id"]
|
||
# On conserve aussi l'id pour FORCE_ID
|
||
insert_cols_with_id = ["id"] + insert_cols
|
||
|
||
placeholders = ", ".join(["%s"] * len(insert_cols_with_id))
|
||
col_names = ", ".join(insert_cols_with_id)
|
||
update_expr = ", ".join(f"{c} = EXCLUDED.{c}" for c in insert_cols)
|
||
|
||
sql = f"""
|
||
INSERT INTO clients ({col_names})
|
||
VALUES ({placeholders})
|
||
ON CONFLICT (username) DO UPDATE SET {update_expr}
|
||
"""
|
||
|
||
conn = psycopg2.connect(**DB_CONFIG)
|
||
conn.autocommit = False
|
||
cur = conn.cursor()
|
||
|
||
inserted = 0
|
||
updated = 0
|
||
errors = 0
|
||
|
||
for i, row in enumerate(rows, 1):
|
||
# Construire un dict {col_name: value} depuis SOURCE_COLUMNS
|
||
row_dict = {}
|
||
for pos, col in enumerate(SOURCE_COLUMNS):
|
||
if col is None:
|
||
continue
|
||
row_dict[col] = row[pos] if pos < len(row) else None
|
||
|
||
values = [row_dict.get(c) for c in insert_cols_with_id]
|
||
username = row_dict.get("username", "?")
|
||
|
||
try:
|
||
if DRY_RUN:
|
||
print(f" [DRY-RUN] ligne {i} : {username} → {row_dict}")
|
||
inserted += 1
|
||
continue
|
||
|
||
cur.execute("SELECT 1 FROM clients WHERE username = %s", (username,))
|
||
exists = cur.fetchone() is not None
|
||
|
||
cur.execute(sql, values)
|
||
|
||
if exists:
|
||
updated += 1
|
||
else:
|
||
inserted += 1
|
||
|
||
except Exception as e:
|
||
print(f" ❌ Erreur ligne {i} ({username}) : {e}")
|
||
conn.rollback()
|
||
errors += 1
|
||
continue
|
||
|
||
if not DRY_RUN:
|
||
cur.execute(
|
||
"SELECT setval('clients_id_seq', COALESCE((SELECT MAX(id) FROM clients), 1))"
|
||
)
|
||
conn.commit()
|
||
|
||
cur.close()
|
||
conn.close()
|
||
|
||
print(f"\n{'🔍 DRY-RUN — ' if DRY_RUN else ''}✅ Terminé")
|
||
print(f" Insérés : {inserted}")
|
||
print(f" Mis à jour : {updated}")
|
||
print(f" Erreurs : {errors}")
|
||
print(f" ℹ️ Séquence clients_id_seq resynchronisée sur MAX(id)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|