data_mover.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import sys
  2. import json
  3. import xlrd3 as xlrd
  4. from openpyxl import load_workbook
  5. class Writer:
  6. """
  7. This class is made to update existing excel file.
  8. First it will open the file in python and then we can do multiple writes and once everything is update we can use
  9. save method in order to save the updated excel file. Hence, this class is very useful is saving time while updating
  10. excel files.
  11. """
  12. def __init__(self, path, sheet):
  13. self.path = path
  14. self.sht = sheet
  15. self.workbook = load_workbook(self.path)
  16. self.sheet = self.workbook[self.sht]
  17. def write(self, row, col, data):
  18. # Update the values using row and col number.
  19. # Note :- We are using openpyxl so row & column index will start from 1 instead of 0
  20. self.sheet.cell(row, col).value = data
  21. def save(self):
  22. # Call this method to save the file once every updates are written
  23. self.workbook.save(self.path)
  24. self.workbook.close()
  25. class Mover:
  26. """
  27. This class is used to copy data from one excel file to another. We can also use some filters to copy only targeted
  28. data.
  29. """
  30. def __init__(self, source_file_path, source_sheet, destination_file_path, destination_sheet, lines=0):
  31. """
  32. :param source_file_path: Path with filename of source excel file
  33. :param source_sheet: Sheet name of the source
  34. :param destination_file_path: Path with filename of Destination excel file
  35. :param destination_sheet: Sheet name of the destination
  36. :param lines: Number of lines starting from 1 to be considered for moving from source.
  37. """
  38. self.source_file_path = source_file_path
  39. self.source_sheet = source_sheet
  40. self.destination_file_path = destination_file_path
  41. self.destination_sheet = destination_sheet
  42. self.lines = lines
  43. def move(self, filters={}, add_missing_columns=False):
  44. """
  45. :param filters: dictionary of filters with header as key and data as value or list of data as value
  46. :param add_missing_columns: True if we need all headers from source file to destination file which are currently
  47. not in destination file.
  48. :return:
  49. """
  50. source = self.read_xlsx(self.source_file_path, self.source_sheet)
  51. destination = self.read_xlsx(self.destination_file_path, self.destination_sheet)
  52. destination_wb = Writer(self.destination_file_path, self.destination_sheet) # Writer class object used to update existing file
  53. if add_missing_columns:
  54. self.add_missing_columns(source, destination, destination_wb)
  55. # again opening destination file as it is updated with the source file columns
  56. destination = self.read_xlsx(self.destination_file_path, self.destination_sheet)
  57. end = self.lines or source.nrows # if number of rows to be considered from source is not predefined the take all
  58. new_data = [source.row(row) for row in range(1, end)] # create a new list of all data and remove the filtered data
  59. remove_data = [] # rows not matching filter are stored here which is used later to remove data from new_data
  60. for filter in filters: # iterate through the dictionary of filter and
  61. ind = [x.value for x in source.row(0)].index(filter) # getting index of filter header then use the same index to check data
  62. if type(filters[filter]) is not list:
  63. filters[filter] = [filters[filter]]
  64. for row in new_data:
  65. if row[ind].value not in filters[filter]: # check if data is matching with filter
  66. remove_data.append(row)
  67. for row in remove_data: # removing unmatched data from new_data list
  68. new_data.remove(row)
  69. row_num = destination.nrows # used to maintain new row number
  70. for data in new_data: # iterating through the data to be written and writing then on the correct cells
  71. row_num += 1
  72. for cell in range(len(data)):
  73. try:
  74. # getting column number where new data is to be written with the help of indexing header in destination file
  75. ind = [x.value for x in destination.row(0)].index(source.row(0)[cell].value) + 1
  76. except ValueError:
  77. # if add_missing_columns is false then ValueError is thrown for the headers which are not present in destination
  78. continue
  79. destination_wb.write(row_num, ind, data[cell].value)
  80. destination_wb.save()
  81. def read_xlsx(self, path, sheet):
  82. # reading xlsx file using xlrd
  83. wb = xlrd.open_workbook(path)
  84. sht = wb.sheet_by_name(sheet)
  85. return sht
  86. def add_missing_columns(self, source, destination, destination_wb):
  87. # looking for the headers which are not present in destination file and then updating destination file
  88. source_headers = [x.value for x in source.row(0)]
  89. destination_headers = [x.value for x in destination.row(0)]
  90. col = len(destination_headers)
  91. for headers in source_headers:
  92. if headers not in destination_headers:
  93. col += 1
  94. destination_wb.write(1, col, headers)
  95. destination_wb.save()
  96. def parse_json(path):
  97. """
  98. Takes parameter from a json file.
  99. :param path:
  100. :return:
  101. """
  102. js = json.load(open(path))
  103. lines = int(js["lines"]) or 0
  104. mover = Mover(js["source_file"], js["source_sheet"], js["destination_file"], js["destination_sheet"], lines)
  105. if js["add_missing_columns"].lower() == "true":
  106. add_missing = True
  107. else:
  108. add_missing = False
  109. mover.move(js["filters"], add_missing)
  110. if __name__ == "__main__":
  111. js = sys.argv[-1]
  112. if ".json" in js.lower():
  113. parse_json(js)