question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Promote warnings to assertion failures and check preconditions at the API calls.

See original GitHub issue

The 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:closed
  • Created 5 years ago
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

3reactions
kjosibcommented, Sep 12, 2018

Without the patch:

X:\>py -m one.off.issue_569
C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\utility.py:34: UserWarning: Col number -4 must be >= 0
  warn("Col number %d must be >= 0" % col)
Traceback (most recent call last):
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 193, in _run_module_as_main
	"__main__", mod_spec)
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 85, in _run_code
	exec(code, run_globals)
        File "X:\one\off\issue_569.py", line 5, in <module>
    sheet.write_cell(2, 2, None)
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\workbook.py", line 158, in __exit__
	self.close()
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\workbook.py", line 306, in close
	self._store_workbook()
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\workbook.py", line 649, in _store_workbook
	xml_files = packager._create_package()
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\packager.py", line 132, in _create_package
	self._write_worksheet_files()
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\packager.py", line 190, in _write_worksheet_files
	worksheet._assemble_xml_file()
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\worksheet.py", line 3740, in _assemble_xml_file
	self._write_merge_cells()
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\worksheet.py", line 5805, in _write_merge_cells
	self._write_merge_cell(merged_range)
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\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'

With the patch:

X:\>py -m one.off.issue_569
Traceback (most recent call last):
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 193, in _run_module_as_main
	"__main__", mod_spec)
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 85, in _run_code
	exec(code, run_globals)
  File "X:\one\off\issue_569.py", line 4, in <module>
	sheet.merge_range(1,2,3,-4, 'FooBar')
  File "C:\Users\kjo32271\AppData\Local\Programs\Python\Python37-32\lib\site-packages\xlsxwriter\worksheet.py", line 97, in cell_wrapper
	assert all(args[i] >= 0 for i in range(4)), 'All coordinates must be non-negative; got '+repr(list(args[0:4]))
AssertionError: All coordinates must be non-negative; got [1, 2, 3, -4]
0reactions
jmcnamaracommented, Sep 22, 2020

@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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Should I assert the preconditions of functions in a public API?
To my understanding, asserting logic errors for debugging purposes is encouraged, but is it good practice to use asserts for validating function ...
Read more >
Programming With Assertions - Oracle Help Center
An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or...
Read more >
Warnings - Ads API - Google Developers
In the Google Ads API, you can request that non-blocking errors in requests be returned in the response. This feature—called warnings—lets you handle...
Read more >
Bug Patterns - Error Prone
Instant APIs only work for NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS and DAYS. InvalidJavaTimeConstant. This checker errors on calls to java.
Read more >
Getting Start With Unit Test for an HTTP REST Application with ...
That brings us to one of the most common RDP APIs failure scenarios, applications request data from RDP with an expired access token....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found