Skip to content

Commit dfbe862

Browse files
author
Luis M. Santos
committed
Added function for converting column count into the correct column label such that 1..26 => A..Z.
Added update_cells and append_rows methods in excel to simulated the steps necessary to add and update cells and rows at the Worksheet level (no table id necessary)
1 parent 800d6ef commit dfbe862

File tree

3 files changed

+72
-1
lines changed

3 files changed

+72
-1
lines changed

O365/excel.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from urllib.parse import quote
1111

1212
from .drive import File
13-
from .utils import ApiComponent, TrackerSet, to_snake_case
13+
from .utils import ApiComponent, TrackerSet, to_snake_case, col_index_to_label
1414

1515
log = logging.getLogger(__name__)
1616

@@ -1948,6 +1948,55 @@ def add_named_range(self, name, reference, comment="", is_formula=False):
19481948
parent=self, **{self._cloud_data_key: response.json()}
19491949
)
19501950

1951+
def update_cells(self, address, rows):
1952+
"""
1953+
Updates the cells at a given range in this worksheet. This is a convenience method since there is no
1954+
direct endpoint API for tableless row updates.
1955+
:param str|Range address: the address to resolve to a range which can be used for updating cells.
1956+
:param list[list[str]] rows: list of rows to push to this range. If updating a single cell, pass a list
1957+
containing a single row (list) containing a single cell worth of data.
1958+
"""
1959+
if isinstance(address, str):
1960+
address = self.get_range(address)
1961+
1962+
if not isinstance(address, Range):
1963+
raise ValueError("address was not an accepted type: str or Range")
1964+
1965+
if not isinstance(rows, list):
1966+
raise ValueError("rows was not an accepted type: list[list[str]]")
1967+
1968+
# Let's not even try pushing to API if the range rectangle mismatches the input row and column count.
1969+
row_count = len(rows)
1970+
col_count = len(rows[0]) if row_count > 0 else 1
1971+
1972+
if address.row_count != row_count or address.column_count != col_count:
1973+
raise ValueError("rows and columns are not the same size as the range selected. This is required by the Microsoft Graph API.")
1974+
1975+
address.values = rows
1976+
address.update()
1977+
1978+
def append_rows(self, rows):
1979+
"""
1980+
Appends rows to the end of a worksheet. There is no direct Graph API to do this operation without a Table
1981+
instance. Instead, this method identifies the last row in the worksheet and requests a range after that row
1982+
and updates that range.
1983+
:param list[list[str]] rows: list of rows to push to this range. If updating a single cell, pass a list
1984+
containing a single row (list) containing a single cell worth of data.
1985+
"""
1986+
row_count = len(rows)
1987+
col_count = len(rows[0]) if row_count > 0 else 0
1988+
1989+
# Find the last row index so we can grab a range after it.
1990+
current_range = self.get_used_range()
1991+
target_index = current_range.row_count
1992+
1993+
# Generate the address needed to outline the bounding rectangle to use to fill in data.
1994+
col_name = col_index_to_label(col_count)
1995+
insert_range_address = 'A{}:{}{}'.format(target_index + 1, col_name, target_index + row_count)
1996+
1997+
# Request to push the data to the given range.
1998+
self.update_cells(insert_range_address, rows)
1999+
19512000
def get_named_range(self, name):
19522001
"""Retrieves a Named range by it's name"""
19532002
url = self.build_url(self._endpoints.get("get_named_range").format(name=name))

O365/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .utils import NEXT_LINK_KEYWORD, ME_RESOURCE, USERS_RESOURCE
66
from .utils import OneDriveWellKnowFolderNames, Pagination, Query
77
from .token import BaseTokenBackend, FileSystemTokenBackend, FirestoreBackend, AWSS3Backend, AWSSecretsBackend, EnvTokenBackend, BitwardenSecretsManagerBackend, DjangoTokenBackend
8+
from .range import col_index_to_label
89
from .windows_tz import get_iana_tz, get_windows_tz
910
from .consent import consent_input_token
1011
from .casing import to_snake_case, to_pascal_case, to_camel_case

O365/utils/range.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
CAPITALIZED_ASCII_CODE = 64
3+
CAPITALIZED_WINDOW = 26
4+
5+
def col_index_to_label(col_index):
6+
"""
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.
11+
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.
14+
"""
15+
label = chr(CAPITALIZED_ASCII_CODE + col_index % CAPITALIZED_WINDOW)
16+
col_index -= CAPITALIZED_WINDOW
17+
18+
while col_index >= CAPITALIZED_WINDOW:
19+
label += chr(CAPITALIZED_ASCII_CODE + col_index % CAPITALIZED_WINDOW)
20+
col_index -= CAPITALIZED_WINDOW
21+
return label

0 commit comments

Comments
 (0)