Coverage for src/artemis_sg/spreadsheet.py: 86%
217 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-05 09:33 -0700
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-05 09:33 -0700
1import logging
2import math
3import os
4import re
5from copy import copy
6from inspect import getsourcefile
8from googleapiclient.discovery import build
9from openpyxl import load_workbook
10from openpyxl.drawing.image import Image
11from openpyxl.styles import Alignment
12from openpyxl.utils import get_column_letter
13from openpyxl.utils.exceptions import InvalidFileException
14from openpyxl.worksheet.dimensions import ColumnDimension, DimensionHolder
15from PIL import Image as PIL_Image
16from PIL import UnidentifiedImageError
18import artemis_sg.app_creds as app_creds
19import artemis_sg.vendor as vendor
20from artemis_sg.config import CFG
22MODULE = os.path.splitext(os.path.basename(__file__))[0]
25def get_worksheet(wb_obj, worksheet):
26 if not worksheet:
27 ws = wb_obj.worksheets[0]
28 else:
29 ws = wb_obj[worksheet]
30 return ws
33def sheet_image(vendor_code, workbook, worksheet, image_directory, out):
34 namespace = f"{MODULE}.{sheet_image.__name__}"
36 image_row_height = CFG["asg"]["spreadsheet"]["sheet_image"]["image_row_height"]
37 image_col_width = CFG["asg"]["spreadsheet"]["sheet_image"]["image_col_width"]
38 isbn_col_width = CFG["asg"]["spreadsheet"]["sheet_image"]["isbn_col_width"]
39 max_col_width = CFG["asg"]["spreadsheet"]["sheet_image"]["max_col_width"]
40 col_buffer = CFG["asg"]["spreadsheet"]["sheet_image"]["col_buffer"]
42 # get vendor info from database
43 logging.debug(f"{namespace}: Instantiate vendor.")
44 vendr = vendor.Vendor(vendor_code)
45 vendr.set_vendor_data()
47 isbn_key = vendr.isbn_key
48 logging.debug(f"{namespace}: Setting ISBN_KEY to '{isbn_key}'.")
50 # Load worksheet
51 logging.info(f"{namespace}: Workbook is {workbook}")
52 wb = load_workbook(workbook)
53 ws = get_worksheet(wb, worksheet)
54 logging.info(f"{namespace}: Worksheet is {ws.title}")
56 # Insert column "A" for "ISBN"
57 # Insert column "B" for "Image"
58 # Insert column "C" for "Order"
59 ws.insert_cols(1)
60 ws.insert_cols(1)
61 ws.insert_cols(1)
62 ws["B1"] = "Image"
63 ws["C1"] = "Order"
65 # Move ISBN colum to "A"
66 # Find ISBN column
67 row01 = ws[1]
68 for cell in row01: 68 ↛ 79line 68 didn't jump to line 79, because the loop on line 68 didn't complete
69 if type(cell.value) is str and cell.value.upper() == isbn_key.upper():
70 # Copy formatting
71 h_font = copy(cell.font)
72 h_border = copy(cell.border)
73 h_fill = copy(cell.fill)
74 h_number_format = copy(cell.number_format)
75 h_protection = copy(cell.protection)
76 h_alignment = copy(cell.alignment)
77 break
78 # Move it to A
79 isbn_idx = cell.column
80 ws.move_range(
81 f"{cell.column_letter}{cell.row}:{cell.column_letter}{ws.max_row}",
82 cols=-(cell.column - 1),
83 )
84 ws.delete_cols(isbn_idx)
86 # Copy ISBN header format to B1, C1
87 cell = ws["A1"]
88 if cell.has_style: 88 ↛ 99line 88 didn't jump to line 99, because the condition on line 88 was never false
89 # fmt: off
90 ws["B1"].font = ws["C1"].font = h_font # noqa: E221, E501
91 ws["B1"].border = ws["C1"].border = h_border # noqa: E221, E501
92 ws["B1"].fill = ws["C1"].fill = h_fill # noqa: E221, E501
93 ws["B1"].number_format = ws["C1"].number_format = h_number_format # noqa: E501
94 ws["B1"].protection = ws["C1"].protection = h_protection # noqa: E221, E501
95 ws["B1"].alignment = ws["C1"].alignment = h_alignment # noqa: E221, E501
96 # fmt: on
98 # Create column widths
99 dim_holder = DimensionHolder(worksheet=ws)
101 # Set column A isbn_col_width for ISBN numbers
102 dim_holder["A"] = ColumnDimension(
103 ws, index="A", width=math.ceil(isbn_col_width * col_buffer)
104 )
105 # Set column B image_col_width for images
106 dim_holder["B"] = ColumnDimension(ws, index="B", width=image_col_width)
108 # Dynamically set remaining columns
109 for col in range(3, ws.max_column + 1):
110 col_letter = get_column_letter(col)
111 width = max(len(str(cell.value)) for cell in ws[col_letter]) * col_buffer
112 if width > max_col_width: 112 ↛ 113line 112 didn't jump to line 113, because the condition on line 112 was never true
113 width = max_col_width
114 dim_holder[col_letter] = ColumnDimension(ws, index=col_letter, width=width)
116 ws.column_dimensions = dim_holder
118 # Prepare column "B" for "Image"
119 colB = ws["B"]
120 for cell in colB:
121 # Format to center content
122 cell.alignment = Alignment(horizontal="center")
124 # Insert images in column 2, (i.e. "B")
125 for row in ws.iter_rows(min_row=2, max_col=2):
126 isbn_cell, image_cell = row
127 if isbn_cell.value: 127 ↛ 141line 127 didn't jump to line 141, because the condition on line 127 was never false
128 isbn = isbn_cell.value
129 if isinstance(isbn, float):
130 isbn = int(isbn)
131 elif isinstance(isbn, str): 131 ↛ 132line 131 didn't jump to line 132, because the condition on line 131 was never true
132 m = re.search('="(.*)"', isbn)
133 if m:
134 isbn = m.group(1)
135 try:
136 isbn = str(isbn).strip()
137 except Exception as e:
138 logging.error(f"{namespace}: Err reading isbn '{isbn}', err: '{e}'")
139 isbn = ""
140 else:
141 isbn = ""
142 # Set row height
143 row_dim = ws.row_dimensions[image_cell.row]
144 row_dim.height = image_row_height
146 # Insert image into cell
147 filename = f"{isbn}.jpg"
148 filepath = os.path.join(image_directory, filename)
149 logging.debug(f"{namespace}: Attempting to insert '{filepath}'.")
150 if os.path.isfile(filepath):
151 img = Image(filepath)
152 ws.add_image(img, f"{image_cell.column_letter}{image_cell.row}")
153 logging.info(f"{namespace}: Inserted '{filepath}'.")
155 # Save workbook
156 wb.save(out)
159def get_order_items(vendor_code, workbook, worksheet):
160 namespace = f"{MODULE}.{get_order_items.__name__}"
162 order_items = []
163 # get vendor info from database
164 logging.debug(f"{namespace}: Instantiate vendor.")
165 vendr = vendor.Vendor(vendor_code)
166 vendr.set_vendor_data()
168 isbn_key = vendr.isbn_key
169 logging.debug(f"{namespace}: Setting ISBN_KEY to '{isbn_key}'.")
171 # Load worksheet
172 logging.info(f"{namespace}: Workbook is {workbook}")
173 wb = load_workbook(workbook)
174 ws = get_worksheet(wb, worksheet)
175 logging.info(f"{namespace}: Worksheet is {ws.title}")
177 # Find Isbn and Order column letters
178 row01 = ws[1]
179 for cell in row01:
180 if cell.value == isbn_key:
181 isbn_column_letter = cell.column_letter
182 if cell.value == "Order":
183 order_column_letter = cell.column_letter
185 for row in ws.iter_rows(min_row=2):
186 for cell in row:
187 if cell.column_letter == isbn_column_letter:
188 isbn_cell = cell
189 if cell.column_letter == order_column_letter:
190 order_cell = cell
191 # Validate ISBN
192 if not isbn_cell.value: 192 ↛ 193line 192 didn't jump to line 193, because the condition on line 192 was never true
193 continue
194 else:
195 isbn = isbn_cell.value
196 if isinstance(isbn, str): 196 ↛ 197line 196 didn't jump to line 197, because the condition on line 196 was never true
197 m = re.search('="(.*)"', isbn)
198 if m:
199 isbn = m.group(1)
200 try:
201 isbn = str(int(isbn)).strip()
202 except Exception as e:
203 logging.error(f"{namespace}: Err reading isbn '{isbn}', err: '{e}'")
204 continue
205 # Validate Order Qty
206 if not order_cell.value: 206 ↛ 207line 206 didn't jump to line 207, because the condition on line 206 was never true
207 continue
208 else:
209 qty = order_cell.value
210 try:
211 qty = str(int(qty)).strip()
212 except Exception as e:
213 logging.error(f"{namespace}: Err reading Order qty '{qty}', err: '{e}'")
214 continue
215 order_items.append((isbn, qty))
217 return order_items
220def mkthumbs(image_directory):
221 namespace = f"{MODULE}.{mkthumbs.__name__}"
223 thumb_width = CFG["asg"]["spreadsheet"]["mkthumbs"]["width"]
224 thumb_height = CFG["asg"]["spreadsheet"]["mkthumbs"]["height"]
226 here = os.path.dirname(getsourcefile(lambda: 0)) 226 ↛ exitline 226 didn't run the lambda on line 226
227 data = os.path.abspath(os.path.join(here, "data"))
228 logo = os.path.join(data, "artemis_logo.png")
229 logging.debug(f"{namespace}: Found image for thumbnail background at '{logo}'")
230 sub_dir = "thumbnails"
231 back = PIL_Image.open(logo)
232 thumb_dir = os.path.join(image_directory, sub_dir)
233 logging.debug(f"{namespace}: Defining thumbnail directory as '{thumb_dir}'")
234 if not os.path.isdir(thumb_dir): 234 ↛ 244line 234 didn't jump to line 244, because the condition on line 234 was never false
235 logging.debug(f"{namespace}: Creating directory '{thumb_dir}'")
236 os.mkdir(thumb_dir)
237 if os.path.isdir(thumb_dir): 237 ↛ 240line 237 didn't jump to line 240, because the condition on line 237 was never false
238 logging.info(f"{namespace}: Successfully created directory '{thumb_dir}'")
239 else:
240 logging.error(
241 f"{namespace}: Failed to create directory '{thumb_dir}'. Aborting."
242 )
243 raise Exception
244 files = os.listdir(image_directory)
245 for f in files:
246 # Valid files are JPG or PNG that are not supplemental images.
247 image = re.match(r"^.+\.(?:jpg|png)$", f)
248 if not image:
249 continue
250 # Supplemental images have a "-[0-9]+" suffix before the file type.
251 # AND a file without that suffix exists int he image_directory.
252 suffix = re.match(r"(^.+)-[0-9]+(\.(?:jpg|png))$", f)
253 if suffix:
254 primary = suffix.group(1) + suffix.group(2)
255 primary_path = os.path.join(image_directory, primary)
256 if os.path.isfile(primary_path):
257 continue
258 thumb_file = os.path.join(thumb_dir, f)
259 # don't remake thumbnails
260 if os.path.isfile(thumb_file): 260 ↛ 261line 260 didn't jump to line 261, because the condition on line 260 was never true
261 continue
262 bk = back.copy()
263 try:
264 file_path = os.path.join(image_directory, f)
265 fg = PIL_Image.open(file_path)
266 except UnidentifiedImageError:
267 logging.error(f"{namespace}: Err reading '{f}', deleting '{file_path}'")
268 os.remove(file_path)
269 continue
270 fg.thumbnail((thumb_width, thumb_height))
271 size = (int((bk.size[0] - fg.size[0]) / 2), int((bk.size[1] - fg.size[1]) / 2))
272 bk.paste(fg, size)
273 logging.debug(f"{namespace}: Attempting to save thumbnail '{thumb_file}'")
274 bkn = bk.convert("RGB")
275 bkn.save(thumb_file)
276 logging.info(f"{namespace}: Successfully created thumbnail '{thumb_file}'")
279def get_sheet_data(workbook, worksheet=None):
280 namespace = f"{MODULE}.{get_sheet_data.__name__}"
281 #########################################################################
282 # Try to open sheet_id as an Excel file
283 sheet_data = []
284 try:
285 wb = load_workbook(workbook)
286 ws = get_worksheet(wb, worksheet)
287 for row in ws.values:
288 sheet_data.append(row)
289 except (FileNotFoundError, InvalidFileException):
290 #########################################################################
291 # Google specific stuff
292 # authenticate to google sheets
293 logging.info(f"{namespace}: Authenticating to google api.")
294 creds = app_creds.app_creds()
295 SHEETS = build("sheets", "v4", credentials=creds)
296 # get sheet data
297 if not worksheet: 297 ↛ 306line 297 didn't jump to line 306, because the condition on line 297 was never false
298 sheets = (
299 SHEETS.spreadsheets()
300 .get(spreadsheetId=workbook)
301 .execute()
302 .get("sheets", "")
303 )
304 ws = sheets.pop(0).get("properties", {}).get("title")
305 else:
306 ws = worksheet
307 sheet_data = (
308 SHEETS.spreadsheets()
309 .values()
310 .get(range=ws, spreadsheetId=workbook)
311 .execute()
312 .get("values")
313 )
314 #########################################################################
315 return sheet_data