11from typing import Optional
22import operator
33import pathlib
4+ import re
45import xlwings as xw
56from rich .text import Text
67from rich .progress import Progress , TextColumn , SpinnerColumn , BarColumn , MofNCompleteColumn , TaskProgressColumn , TimeRemainingColumn
1314def excel_runner (
1415 xlsx_filepath ,
1516 demand_input_cell_arrays : dict [str , list ],
16- identifier_cell_arrays : dict [str , list ],
1717 design_inputs : dict [str , dict [str , float ]],
1818 result_cells : list [str ],
1919 save_conditions : dict [str , callable ],
20+ identifier_keys : Optional [list [str ]] = None ,
2021 save_dir : Optional [str ] = None ,
2122 sheet_idx : int = 0 ,
2223) -> None :
24+ """
25+ Doc strign
26+ """
27+
2328 demand_cell_ids = list (demand_input_cell_arrays .keys ())
2429 iterations = len (demand_input_cell_arrays [demand_cell_ids [0 ]])
2530
@@ -43,35 +48,47 @@ def excel_runner(
4348 main_task = main_progress .add_task ("Primary Iterations" , total = iterations )
4449 with Live (panel ) as live :
4550 for iteration in range (iterations ):
46- demand_cells_to_change = {cell_id : demand_input_cell_arrays [cell_id ][iteration ] for cell_id in demand_cell_ids }
51+ demand_cells_to_change = {
52+ cell_id : demand_input_cell_arrays [cell_id ][iteration ]
53+ for cell_id in demand_cell_ids
54+ if valid_excel_reference (cell_id )
55+ }
56+ identifier_values = {
57+ cell_id : str (demand_input_cell_arrays [cell_id ][iteration ])
58+ for cell_id in demand_cell_ids
59+ if not valid_excel_reference (cell_id )
60+ }
4761 variations_task = variations_progress .add_task ("Sheet variations" , total = len (design_inputs .items ()))
4862 variations_progress .reset (variations_task )
4963 for design_tag , design_cells_to_change in design_inputs .items ():
5064 cells_to_change = demand_cells_to_change | design_cells_to_change
51- calculated_results = excel_engine (
65+ calculated_results = execute_workbook (
5266 xlsx_filepath ,
5367 cells_to_change = cells_to_change ,
5468 cells_to_retrieve = result_cells ,
5569 sheet_idx = sheet_idx
5670 )
5771
5872 save_condition_acc = []
59- for idx , result_cell_id in enumerate ( result_cells ):
60- calculated_result = calculated_results [idx ]
61- save_condition_acc .append (save_conditions [ result_cell_id ] (calculated_result ))
73+ for result_cell_id , save_condition in save_conditions . items ( ):
74+ calculated_result = calculated_results [result_cell_id ]
75+ save_condition_acc .append (save_condition (calculated_result ))
6276 variations_progress .update (variations_task , advance = 1 )
6377
6478 if all (save_condition_acc ):
6579 filepath = pathlib .Path (xlsx_filepath )
6680 name = filepath .stem
6781 suffix = filepath .suffix
68- demand_ids = "-" .join ([str (id_array [iteration ]) for id_array in identifier_cell_arrays .values ()])
82+ if identifier_values :
83+ identifiers = "-" .join ([identifier_values [id_key ] for id_key in identifier_values ])
84+ else :
85+ identifiers = f"{ iteration } "
6986
70- new_filename = f"{ name } -{ demand_ids } -{ design_tag } { suffix } "
87+ new_filename = f"{ name } -{ identifiers } -{ design_tag } { suffix } "
7188 save_dir_path = pathlib .Path (save_dir )
7289 if not save_dir_path .exists ():
7390 save_dir_path .mkdir (parents = True )
74- calculated_results = excel_engine (
91+ calculated_results = execute_workbook (
7592 xlsx_filepath ,
7693 cells_to_change = cells_to_change ,
7794 cells_to_retrieve = result_cells ,
@@ -86,7 +103,7 @@ def excel_runner(
86103 main_progress .update (main_task , advance = 1 )
87104
88105
89- def excel_engine (
106+ def execute_workbook (
90107 xlsx_filepath : str | pathlib .Path ,
91108 cells_to_change : dict [str , str | float | int ],
92109 cells_to_retrieve : list [str ],
@@ -103,8 +120,12 @@ def excel_engine(
103120 of a filepath str on Windows, make sure they are escaped.
104121 'cells_to_change': A dictionary where the keys are Excel cell names (e.g. "E45")
105122 and the values are the values that should be set for each key.
106- 'cells_to_retrieve': A list of str representing Excel cell names that should be
107- retrieved after computation (e.g. ['C1', 'E5'])
123+ 'cells_to_retrieve': Either a list or dict. If list, represents a list of str
124+ representing Excel cell names that should be retrieved after computation
125+ (e.g. ['C1', 'E5']).
126+ If a dict, the keys are the cell references and the values are what the
127+ cell references represent. The values will be used as the keys in the
128+ returned dictionary. (e.g. {"C1": "Date", "E5": "Critical value"})
108129 'sheet_idx': The sheet in the workbook
109130 'new_filepath': If not None, a copy of the altered workbook will be saved at this
110131 locations. Can be a str or pathlib.Path. Directories on
@@ -123,13 +144,16 @@ def excel_engine(
123144 except :
124145 raise ValueError (f"Invalid input cell name: { cell_name } . Perhaps you made a typo?" )
125146
126- calculated_values = [] # Add afterwards
147+ calculated_values = {} # Add afterwards
127148 for cell_to_retrieve in cells_to_retrieve :
128149 try :
129150 retrieved_value = ws [cell_to_retrieve ].value
130151 except :
131152 raise ValueError (f"Invalid retrieval cell name: { cell_to_retrieve } . Perhaps you made a typo?" )
132- calculated_values .append (retrieved_value )
153+ label = cell_to_retrieve
154+ if isinstance (cells_to_retrieve , dict ):
155+ label = cells_to_retrieve .get (cell_to_retrieve , cell_to_retrieve )
156+ calculated_values .update ({label : retrieved_value })
133157
134158 if new_filepath :
135159 new_filepath = pathlib .Path (new_filepath )
@@ -168,7 +192,19 @@ def create_condition_check(check_against_value: float, op: str) -> callable:
168192 "ne" : operator .ne ,
169193 }
170194 def checker (test_value ):
171- return operators [op ](test_value , check_against_value )
195+ return operators [op . lower () ](test_value , check_against_value )
172196
173197 return checker
174-
198+
199+
200+ def valid_excel_reference (cell : str ) -> bool :
201+ """
202+ Returns True if 'cell' is a value that represents a valid
203+ MS Excel reference, e.g. "B4", "AAC93290"
204+ """
205+ pattern = re .compile ("^[A-Z]{1,3}[0-9]+$" )
206+ match = pattern .match (cell )
207+ if match is None :
208+ return False
209+ else :
210+ return True
0 commit comments