csv_import.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import plac
  2. from pathlib import Path
  3. import csv
  4. import re
  5. import pandas as pd
  6. from sqlalchemy import create_engine, inspect, event
  7. import json
  8. csv_dir = Path("C:\\GlobalCube\\System\\CARLO\\Export")
  9. target_dsn = { 'user': "sa", 'pass': "Mffu3011#", 'server': "GC-SERVER1\\GLOBALCUBE", 'database': "GC" }
  10. temp_schema = "temp"
  11. target_schema = "carlo"
  12. def get_dtype (db_type):
  13. if db_type == "DATETIME":
  14. return "datetime64"
  15. if db_type == "DECIMAL(28, 8)" or db_type == "DECIMAL(18, 0)" or db_type == "NUMERIC(18, 0)":
  16. return "float64"
  17. return "object"
  18. def conn_string (dsn):
  19. return f"mssql+pyodbc://{dsn['user']}:{dsn['pass']}@{dsn['server']}/{dsn['database']}?driver=SQL+Server+Native+Client+11.0"
  20. def conn_params (dsn):
  21. return f"-S {dsn['server']} -d {dsn['database']} -U {dsn['user']} -P {dsn['pass']}"
  22. def columns_from_csv (source_csv, target_insp_cols):
  23. target_col_dict = dict([(col['name'], get_dtype(str(col['type']))) for col in target_insp_cols if str(col['type']) != "DATETIME"])
  24. target_dates = [col['name'] for col in target_insp_cols if str(col['type']) == "DATETIME"]
  25. df = pd.read_csv(source_csv, sep=";", encoding="ansi", decimal=",", dtype=target_col_dict, parse_dates=target_dates, nrows=10)
  26. source_columns_list = list(df.columns)
  27. for i,col in enumerate([col for col in source_columns_list if col[-2:] == ".1"]):
  28. source_columns_list[i] = col[:-2] + "2"
  29. return source_columns_list
  30. def transform_template(target_insp, source_csv, table, target_schema):
  31. target_insp_cols = target_insp.get_columns(table, schema=target_schema)
  32. target_columns_list = [col['name'] for col in target_insp_cols]
  33. source_columns_list = columns_from_csv(source_csv, target_insp_cols)
  34. target_columns = set(target_columns_list)
  35. source_columns = set(source_columns_list)
  36. #intersect = source_columns.intersection(target_columns)
  37. #print("Auf beiden Seiten: " + ";".join(intersect))
  38. diff1 = source_columns.difference(target_columns)
  39. if len(diff1) > 0:
  40. print("Nur in Quelle: " + ";".join(diff1))
  41. diff2 = target_columns.difference(source_columns)
  42. if len(diff2) > 0:
  43. print("Nur in Ziel: " + ";".join(diff2))
  44. transform = []
  45. for i, col in enumerate(target_columns_list):
  46. if col in source_columns_list:
  47. pos = source_columns_list.index(col)
  48. else:
  49. pos = -1
  50. transform.append((pos, get_dtype(str(target_insp_cols[i]['type']))))
  51. return transform
  52. def transform_line (line, transform):
  53. pattern = re.compile(r"\d{4}-\d\d-\d\d \d\d:\d\d")
  54. result = []
  55. for pos, f in transform:
  56. e = ""
  57. if pos > -1:
  58. e = line[pos]
  59. if f == "float64":
  60. e = e.replace(",", ".")
  61. if f == "datetime64":
  62. if not pattern.match(e):
  63. e = ""
  64. #e += ":00.000"
  65. result.append(e)
  66. return result
  67. def transform_file(source_csv, transform):
  68. stage_csv = Path("\\".join(source_csv.parts[:-1] + list("stage") + source_csv.parts[-1:]))
  69. if stage_csv.exists() and stage_csv.stat()['st_ctime'] > source_csv.stat()['st_ctime']:
  70. print(f"Stage-CSV '{stage_csv.name}' ist bereits aktuell.")
  71. return False
  72. with csv.reader(open(source_csv, "r", encoding="ansi", newline=""), delimiter=";") as source_file:
  73. with csv.writer(open(stage_csv, "w", encoding="utf8", newline=""), delimiter="\t") as target_file:
  74. next(source_file) # ignore header
  75. for cols in source_file:
  76. target_file.writerow(transform_line(cols, transform))
  77. def csv_tables (csv_dir, target_tables_ci):
  78. p = re.compile("_\d+$")
  79. result = []
  80. if not csv_dir.is_dir():
  81. print(f"Verzeichnis {csv_dir} existiert nicht!")
  82. return result
  83. for source_csv in csv_dir.glob("*.csv"):
  84. if source_csv.is_dir():
  85. continue
  86. table = source_csv.name[:-4].lower()
  87. if not table in target_tables_ci:
  88. table = p.sub("", table)
  89. if not table in target_tables_ci:
  90. print(f"Ziel-Tabelle '{table}' existiert nicht!")
  91. continue
  92. result.append((table, source_csv))
  93. return result
  94. def target_tables (target_dsn, target_schema):
  95. engine = create_engine(conn_string(target_dsn))
  96. target_insp = inspect(engine)
  97. target_tables = target_insp.get_table_names(schema=target_schema)
  98. return (target_insp, list(map(str.lower, target_tables)))
  99. def batch (csv_dir, action):
  100. target_insp, target_tables_ci = target_tables(target_dsn, target_schema)
  101. for table in csv_tables(csv_dir, target_tables_ci):
  102. source_csv = f"{csv_dir}\\{table}.csv"
  103. print(f"=={table}==")
  104. stage_csv = f"{csv_dir}\\stage\\{table}.csv"
  105. print(f"sqlcmd.exe {conn_params(target_dsn)} -p -Q \"TRUNCATE TABLE [{target_schema}].[{table}]\" ")
  106. tf_template = transform_template(target_insp, source_csv, table, target_schema)
  107. print(f"%PYTHON%\\python.exe csv_import.py transform \"{source_csv}\" \"{json.dumps(tf_template)}\" ")
  108. print(f"bcp.exe [{target_schema}].[{table}] in \"{stage_csv}\" {conn_params(target_dsn)} -c -C 65001 -e \"{stage_csv}.log\" ")
  109. print("")
  110. @plac.pos('csv_dir', "", type=Path)
  111. @plac.opt('action', "", choices=['overwrite', 'update', 'transform'])
  112. @plac.opt('template', "")
  113. def main(csv_dir, action="overwrite", template="[]"):
  114. if action == "transform":
  115. transform_file(csv_dir, template)
  116. else:
  117. batch(csv_dir, action)
  118. if __name__ == '__main__':
  119. #plac.call(main)
  120. main(csv_dir)