Promote warnings to assertion failures and check preconditions at the API calls.
See original GitHub issueThe Problem
I was tracing a bizarre failure distant from the true cause when I discovered at utility.py:15
def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
if row < 0:
warn("Row number %d must be >= 0" % row)
return None
# ... etc ...
return col_str + row_abs + str(row)
which seems innocent enough – don’t return anything if nothing can be made of the inputs – but later on, at worksheet.py:5809
, we see the following:
def _write_merge_cell(self, merged_range):
# Write the <mergeCell> element.
(row_min, col_min, row_max, col_max) = merged_range
# Convert the merge dimensions to a cell range.
cell_1 = xl_rowcol_to_cell(row_min, col_min)
cell_2 = xl_rowcol_to_cell(row_max, col_max)
ref = cell_1 + ':' + cell_2
It’s my fault I passed a bad range into worksheet.merge_range(...)
somehow, but now there are two additional problems. The first is that the following exception message:
File "...\lib\site-packages\xlsxwriter\worksheet.py", line 3740, in _assemble_xml_file
self._write_merge_cells()
File "...\lib\site-packages\xlsxwriter\worksheet.py", line 5805, in _write_merge_cells
self._write_merge_cell(merged_range)
File "...\lib\site-packages\xlsxwriter\worksheet.py", line 5816, in _write_merge_cell
ref = cell_1 + ':' + cell_2
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
highlights an incorrect assumption that xl_rowcol_to_cell
necessarily returns a string (it doesn’t always), and also, by the time the workbook is closing I have zero idea how the offending worksheet.merge_range(...)
call happened. Poking around, I find the relevant data structure is the list worksheet.merge, which is written at worksheet.py:1584
in the definition of .merge_range(...)
. Scanning upward, I see that we check the column number of the lower-right corner of the merge range on line 1580 before appending to the list, but:
- if that check fails, no word is passed to client code (I expected an exception), and
- the check neglects to test the upper-left corner of the merge range.
Proposed Solution
Let us “fail early and often”, as follows:
A great many of the API calls are decorated with @convert_..._args
, collectively defined at the top of worksheet.py
. Suppose the wrappers were augmented with code to assert non-negative row/column numbers before delegating to the wrapped method, like this at line 97:
assert all(args[i] >= 0 for i in range(4)), 'All coordinates must be non-negative; got '+repr(list(args[0:4]))
return method(self, *args, **kwargs)
As a result, I get an exception pointing me to exactly the place where the actual problem is. (I’m going to live with this patch until the next upgrade.)
This one tiny change would protect virtually the whole API from the scourge of invalid input parameters. If you felt like it, you could also take care of swapped top-bottom/left-right once-and-only-once in the wrappers within convert_range_args
and convert_column_args
, thus probably saving a metric boatload of repetitious code.
Issue Analytics
- State:
- Created 5 years ago
- Comments:9 (4 by maintainers)
Top GitHub Comments
Without the patch:
With the patch:
@kjosib Thanks for your reply. You are clearly passionate about this. However, as you say, it is probably too late for the library, and for me, to introduce widespread exceptions at this point. So I’ll pass on the patches. Thanks for your input.