Skip to content

Commit 48e9833

Browse files
author
Luis M. Santos
committed
fix refactored the column label generation function and added corresponding unit test.
1 parent 68bfd59 commit 48e9833

File tree

4 files changed

+57
-15
lines changed

4 files changed

+57
-15
lines changed

O365/drive.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,7 @@ def _base_get_list(self, url, limit=None, *, query=None, order_by=None,
16061606
items = (
16071607
self._classifier(item)(parent=self, **{self._cloud_data_key: item})
16081608
for item in data.get('value', []))
1609+
16091610
next_link = data.get(NEXT_LINK_KEYWORD, None)
16101611
if batch and next_link:
16111612
return Pagination(parent=self, data=items,

O365/excel.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1980,11 +1980,21 @@ def append_rows(self, rows):
19801980
Appends rows to the end of a worksheet. There is no direct Graph API to do this operation without a Table
19811981
instance. Instead, this method identifies the last row in the worksheet and requests a range after that row
19821982
and updates that range.
1983+
1984+
Beware! If you open your workbook from sharepoint and delete all of the rows in one go and attempt to append
1985+
new rows, you will get undefined behavior from the Microsoft Graph API. I don't know if I did not give enough
1986+
time for the backend to synchronize from the moment of deletion on my browser and the moment I triggered my
1987+
script, but this is something I have observed. Sometimes insertion fails and sometimes it inserts where the new
1988+
row would have been if data had not been deleted from the browser side. Maybe it is an API cache issue. However,
1989+
after the first row is inserted successfully, this undefined behavior goes away on repeat calls to my scripts.
1990+
Documenting this behavior for future consumers of this API.
1991+
19831992
:param list[list[str]] rows: list of rows to push to this range. If updating a single cell, pass a list
19841993
containing a single row (list) containing a single cell worth of data.
19851994
"""
19861995
row_count = len(rows)
19871996
col_count = len(rows[0]) if row_count > 0 else 0
1997+
col_index = col_count - 1
19881998

19891999
# Find the last row index so we can grab a range after it.
19902000
current_range = self.get_used_range()
@@ -2000,7 +2010,7 @@ def append_rows(self, rows):
20002010
target_index = current_range.row_count
20012011

20022012
# Generate the address needed to outline the bounding rectangle to use to fill in data.
2003-
col_name = col_index_to_label(col_count)
2013+
col_name = col_index_to_label(col_index)
20042014
insert_range_address = 'A{}:{}{}'.format(target_index + 1, col_name, target_index + row_count)
20052015

20062016
# Request to push the data to the given range.

O365/utils/range.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
2-
CAPITALIZED_ASCII_CODE = 64
1+
CAPITALIZED_ASCII_CODE = ord('A')
32
CAPITALIZED_WINDOW = 26
43

4+
55
def col_index_to_label(col_index):
66
"""
7-
Given a column index, returns the label corresponding to the column name. For example, index 1 would be
8-
A ... until 26 which would be Z.
9-
This function will loop until a full label is generated using chunks of 26. Meaning, an index of 52 should
10-
yield a label of ZZ corresponding to the ZZ column.
7+
Given a column index, returns the label corresponding to the column name. For example, index 0 would be
8+
A ... until 25 which would be Z.
9+
This function will recurse until a full label is generated using chunks of CAPITALIZED_WINDOW. Meaning,
10+
an index of 51 should yield a label of ZZ corresponding to the ZZ column.
1111
12-
:param int col_index: list of rows to push to this range. If updating a single cell, pass a list
13-
containing a single row (list) containing a single cell worth of data.
12+
:param int col_index: number associated with the index position of the requested column. For example, column index 0
13+
would correspond to column label A.
1414
"""
15-
label = chr(CAPITALIZED_ASCII_CODE + col_index % CAPITALIZED_WINDOW)
16-
col_index -= CAPITALIZED_WINDOW
15+
label = ''
16+
extra_letter_index = (col_index // CAPITALIZED_WINDOW) - 1 # Minor adjustment for the no repeat (0) case.
17+
18+
# If we do need to prepend a new letter to the column label do so recursively such that we could simulate
19+
# labels like AA or AAA or AAAA ... etc.
20+
if extra_letter_index >= 0:
21+
label += col_index_to_label(extra_letter_index)
22+
23+
# Otherwise, passthrough and add the letter the input index corresponds to.
24+
return label + index_to_col_char(col_index)
1725

18-
while col_index >= CAPITALIZED_WINDOW:
19-
label += chr(CAPITALIZED_ASCII_CODE + col_index % CAPITALIZED_WINDOW)
20-
col_index -= CAPITALIZED_WINDOW
21-
return label
26+
def index_to_col_char(index):
27+
return chr(CAPITALIZED_ASCII_CODE + index % CAPITALIZED_WINDOW)

tests/test_range.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pytest
2+
3+
from O365.utils import col_index_to_label
4+
5+
6+
class TestRange:
7+
EXPECTED_CHARS = [
8+
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
9+
'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
10+
'BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ',
11+
]
12+
def setup_class(self):
13+
pass
14+
15+
def teardown_class(self):
16+
pass
17+
18+
def test_col_index_to_label(self):
19+
for i in range(len(self.EXPECTED_CHARS)):
20+
expected_index = i
21+
expected_label = self.EXPECTED_CHARS[expected_index]
22+
label = col_index_to_label(i)
23+
print(f'Index {i} Letter Index {i} Label {label} Expected {expected_label}')
24+
25+
assert label == expected_label

0 commit comments

Comments
 (0)