Skip to content

Commit f34adeb

Browse files
committed
Implemented basic tests
1 parent 9945624 commit f34adeb

File tree

5 files changed

+119
-18
lines changed

5 files changed

+119
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,4 @@ cython_debug/
150150
# and can be added to the global gitignore or merged into this file. For a more nuclear
151151
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
152152
#.idea/
153+
tests/test_data/stored_results.xlsx

src/xl_engine/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
def main() -> None:
2-
print("Hello from xl-engine!")
1+
"""
2+
A Python package that provides convenience functions using XLWings to parametrize,
3+
execute, and save Excel workbooks.
4+
"""
5+
6+
__version__ = "0.1.0"
7+
8+
from xl_engine.excel_engine import create_condition_check, execute_workbook, excel_runner

src/xl_engine/excel_engine.py

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional
22
import operator
33
import pathlib
4+
import re
45
import xlwings as xw
56
from rich.text import Text
67
from rich.progress import Progress, TextColumn, SpinnerColumn, BarColumn, MofNCompleteColumn, TaskProgressColumn, TimeRemainingColumn
@@ -13,13 +14,17 @@
1314
def 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

tests/test_data/example_wb.xlsx

9.9 KB
Binary file not shown.

tests/test_excel_engine.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import xl_engine as xl
2+
import math
3+
import pathlib
4+
5+
TEST_DATA_DIR = pathlib.Path(__file__).parent / "test_data"
6+
7+
def test_create_condition_check():
8+
func = xl.create_condition_check(1.0, "le")
9+
assert func(2) == False
10+
assert func(1) == True
11+
assert func(0.9) == True
12+
13+
func2 = xl.create_condition_check(2393.90, "gt")
14+
assert func2(3000) == True
15+
assert func2(1) == False
16+
assert func2(2393.90) == False
17+
18+
def test_execute_workbook():
19+
results = xl.execute_workbook(
20+
TEST_DATA_DIR / "example_wb.xlsx",
21+
cells_to_change = {"B1": 33, "B2": 44},
22+
cells_to_retrieve=['B4', 'B5'],
23+
sheet_idx=0,
24+
)
25+
assert results['B4'] == 22
26+
assert results['B5'] == 11
27+
28+
results2 = xl.execute_workbook(
29+
TEST_DATA_DIR / "example_wb.xlsx",
30+
cells_to_change = {"B1": 33, "B2": 66},
31+
cells_to_retrieve=['B4', 'B5'],
32+
sheet_idx=1,
33+
new_filepath=TEST_DATA_DIR / "stored_results.xlsx"
34+
)
35+
assert results2['B4'] == 44
36+
assert math.isclose(results2['B5'], 39.6)
37+
38+
import xlwings as xw
39+
with xw.Book(TEST_DATA_DIR / "stored_results.xlsx") as wb:
40+
ws = wb.sheets[1]
41+
assert ws["B1"].value == 33
42+
assert ws["B2"].value == 66
43+
44+
def test_excel_runner():
45+
dcr2 = xl.create_condition_check(2, "ge")
46+
xl.excel_runner(
47+
TEST_DATA_DIR / "example_wb.xlsx",
48+
demand_input_cell_arrays={"B1": [10, 20], "Labels": ["R", "S"]},
49+
design_inputs={
50+
"OptA": {"B2": 22},
51+
"OptB": {"B2": 33},
52+
"OptC": {"B2": 55},
53+
},
54+
result_cells=["B4", "B5", "B6"],
55+
save_conditions={"B6": dcr2},
56+
identifier_keys=["Labels"],
57+
save_dir=TEST_DATA_DIR / "design"
58+
)

0 commit comments

Comments
 (0)