Skip to content

Commit

Permalink
Merge branch 'master' into feature/merge_master
Browse files Browse the repository at this point in the history
  • Loading branch information
lavigne958 committed Dec 14, 2023
2 parents 9df2564 + 495a329 commit 7cf5d0c
Show file tree
Hide file tree
Showing 23 changed files with 11,034 additions and 4,928 deletions.
16 changes: 16 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
Release History
===============

5.12.1 (2023-12-04)
-------------------

* Many fixes for `get_records` by @alifeee in https://github.com/burnash/gspread/pull/1357
* change `worksheet.update` migration guide by @alifeee in https://github.com/burnash/gspread/pull/1362

5.12.1 (2023-11-29)
-------------------

* feature/readme migration v6 by @lavigne958 in https://github.com/burnash/gspread/pull/1297
* add deprecation warnings for lastUpdateTime... by @alifeee in https://github.com/burnash/gspread/pull/1333
* remove `use_index` and references to it in `get_records` by @alifeee in https://github.com/burnash/gspread/pull/1343
* make deprecation warning dependent on if kwarg is used for client_factory by @alifeee in https://github.com/burnash/gspread/pull/1349
* fix 1352 expected headers broken by @alifeee in https://github.com/burnash/gspread/pull/1353
* fix `combine_merged_cells` when using from a range that doesn't start at `A1` by @alifeee in https://github.com/burnash/gspread/pull/1335

5.12.0 (2023-10-22)
-------------------

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,13 @@ The first two arguments (`values` & `range_name`) have swapped (to `range_name`
As well, `values` can no longer be a list, and must be a 2D array.

```diff
- file.sheet1.update(["54"], "B2")
+ file.sheet1.update(range_name="I7", values=[["54"]])
- file.sheet1.update([["new", "values"]])
+ file.sheet1.update([["new", "values"]]) # unchanged

- file.sheet1.update("B2:C2", [["54", "55"]])
+ file.sheet1.update([["54", "55"]], "B2:C2")
# or
+ file.sheet1.update(range_name="B2:C2", values=[["54", "55"]])
```

### Change colors from dictionary to text
Expand Down
30 changes: 23 additions & 7 deletions gspread/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ def is_scalar(x: Any) -> bool:
return isinstance(x, str) or not isinstance(x, Sequence)


def combined_merge_values(worksheet_metadata, values):
def combined_merge_values(worksheet_metadata, values, start_row_index, start_col_index):
"""For each merged region, replace all values with the value of the top-left cell of the region.
e.g., replaces
[
Expand All @@ -713,6 +713,12 @@ def combined_merge_values(worksheet_metadata, values):
Should have a "merges" key.
:param values: The values returned by the Google API for the worksheet. 2D array.
:param start_row_index: The index of the first row of the values in the worksheet.
e.g., if the values are in rows 3-5, this should be 2.
:param start_col_index: The index of the first column of the values in the worksheet.
e.g., if the values are in columns C-E, this should be 2.
"""
merges = worksheet_metadata.get("merges", [])
# each merge has "startRowIndex", "endRowIndex", "startColumnIndex", "endColumnIndex
Expand All @@ -723,14 +729,24 @@ def combined_merge_values(worksheet_metadata, values):
max_col_index = len(values[0]) - 1

for merge in merges:
start_row, end_row = merge["startRowIndex"], merge["endRowIndex"]
start_col, end_col = merge["startColumnIndex"], merge["endColumnIndex"]
merge_start_row, merge_end_row = merge["startRowIndex"], merge["endRowIndex"]
merge_start_col, merge_end_col = (
merge["startColumnIndex"],
merge["endColumnIndex"],
)
# subtract offset
merge_start_row -= start_row_index
merge_end_row -= start_row_index
merge_start_col -= start_col_index
merge_end_col -= start_col_index
# if out of bounds, ignore
if start_row > max_row_index or start_col > max_col_index:
if merge_start_row > max_row_index or merge_start_col > max_col_index:
continue
if merge_start_row < 0 or merge_start_col < 0:
continue
top_left_value = values[start_row][start_col]
row_indices = range(start_row, end_row)
col_indices = range(start_col, end_col)
top_left_value = values[merge_start_row][merge_start_col]
row_indices = range(merge_start_row, merge_end_row)
col_indices = range(merge_start_col, merge_end_col)
for row_index in row_indices:
for col_index in col_indices:
# if out of bounds, ignore
Expand Down
127 changes: 80 additions & 47 deletions gspread/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def get_records( # noqa: C901 # this comment disables the complexity check for
the Sheets API.
:type value_render_option: :namedtuple:`~gspread.utils.ValueRenderOption`
:param list expected_headers: (optional) List of expected headers, they must be unique.
:param list expected_headers: (optional) Set this to allow reading a spreadsheet with duplicate headers. Set this to a list of unique headers that you want to read. Other headers not included in this list may be overwritten and data lost.
.. note::
Expand Down Expand Up @@ -556,66 +556,69 @@ def get_records( # noqa: C901 # this comment disables the complexity check for

if last_index is None:
last_index = self.row_count
last_index_set = False
elif last_index < first_index:
raise ValueError("last_index must be greater than or equal to first_index")
elif last_index > self.row_count:
raise ValueError(
"last_index must be an integer less than or equal to the number of rows in the worksheet"
)
else:
last_index_set = True

keys = self.get(
"{head}:{head}".format(head=head),
values = self.get_values(
"{first_index}:{last_index}".format(
first_index=first_index, last_index=last_index
),
value_render_option=value_render_option,
return_type=GridRangeType.ListOfLists,
pad_values=True,
)[0]
)
if values == [[]]:
# see test_get_records_with_all_values_blank
# if last index is not asked for,
# we don't know the length of the sheet so we return []
if last_index_set is False:
return []
# otherwise values will later be padded to be the size of keys + sheet size
values = [[]]

keys_row = self.get_values(
"{head}:{head}".format(head=head), value_render_option=value_render_option
)
keys = keys_row[0] if len(keys_row) > 0 else []

values_width = len(values[0])
keys_width = len(keys)
values_wider_than_keys_by = values_width - keys_width

# pad keys and values to be the same WIDTH
if values_wider_than_keys_by > 0:
keys.extend([default_blank] * values_wider_than_keys_by)
elif values_wider_than_keys_by < 0:
values = fill_gaps(values, cols=keys_width, padding_value=default_blank)

# pad values to be the HEIGHT of last_index - first_index + 1
if last_index_set is True:
values = fill_gaps(values, rows=last_index - first_index + 1)

if expected_headers is None:
expected_headers = keys
# all headers must be unique
header_row_is_unique = len(keys) == len(set(keys))
if not header_row_is_unique:
raise GSpreadException("the header row in the worksheet is not unique")
else:
# all expected headers must be unique
expected_headers_are_unique = len(expected_headers) == len(
set(expected_headers)
)
if not expected_headers_are_unique:
raise GSpreadException("the given 'expected_headers' are not uniques")

# validating the headers in the worksheet
header_row_is_unique = len(keys) == len(set(keys))
if not header_row_is_unique:
raise GSpreadException("the header row in the worksheet is not unique")

# validating that the expected headers are part of the headers in the worksheet
if not all(header in keys for header in expected_headers):
raise GSpreadException(
"the given 'expected_headers' contains unknown headers: {}".format(
set(expected_headers) - set(keys)
# expected headers must be a subset of the actual headers
if not all(header in keys for header in expected_headers):
raise GSpreadException(
"the given 'expected_headers' contains unknown headers: {}".format(
set(expected_headers) - set(keys)
)
)
)

values = self.get(
"{first_index}:{last_index}".format(
first_index=first_index, last_index=last_index
),
value_render_option=value_render_option,
return_type=GridRangeType.ListOfLists,
pad_values=True,
)

values_len = len(values[0])
keys_len = len(keys)
values_wider_than_keys_by = values_len - keys_len
default_blank_in_keys = default_blank in keys

if ((values_wider_than_keys_by > 0) and default_blank_in_keys) or (
values_wider_than_keys_by > 1
):
raise GSpreadException(
"the header row in the worksheet contains multiple empty cells"
)
elif values_wider_than_keys_by == 1:
keys.append(default_blank)
elif values_wider_than_keys_by < 0:
values = fill_gaps(values, cols=keys_len, padding_value=default_blank)

if numericise_ignore == ["all"]:
pass
Expand Down Expand Up @@ -969,7 +972,8 @@ def get(
# Return cell values without calculating formulas
worksheet.get('A2:B4', value_render_option=ValueRenderOption.formula)
"""
range_name = absolute_range_name(self.title, range_name)
# do not override the given range name with the build up range name for the actual request
get_range_name = absolute_range_name(self.title, range_name)

params: ParamsType = {
"majorDimension": major_dimension,
Expand All @@ -978,7 +982,7 @@ def get(
}

response = self.client.values_get(
self.spreadsheet_id, range_name, params=params
self.spreadsheet_id, get_range_name, params=params
)

values = response.get("values", [[]])
Expand All @@ -995,7 +999,36 @@ def get(
lambda x: x["properties"]["title"] == self.title,
spreadsheet_meta["sheets"],
)
values = combined_merge_values(worksheet_meta, values)

# deal with named ranges
named_ranges = spreadsheet_meta.get("namedRanges", [])
# if there is a named range with the name range_name
if any(
range_name == ss_namedRange["name"]
for ss_namedRange in named_ranges
if ss_namedRange.get("name")
):
ss_named_range = finditem(
lambda x: x["name"] == range_name, named_ranges
)
grid_range = ss_named_range.get("range", {})
# norrmal range_name, i.e., A1:B2
elif range_name is not None:
a1 = get_a1_from_absolute_range(range_name)
grid_range = a1_range_to_grid_range(a1)
# no range_name, i.e., all values
else:
grid_range = worksheet_meta.get("basicFilter", {}).get("range", {})

values = combined_merge_values(
worksheet_metadata=worksheet_meta,
values=values,
start_row_index=grid_range.get("startRowIndex", 0),
start_col_index=grid_range.get("startColumnIndex", 0),
)

# In case range_name is None
range_name = range_name or ""

# range_name must be a full grid range so that we can guarantee
# startRowIndex and endRowIndex properties
Expand Down
Loading

0 comments on commit 7cf5d0c

Please sign in to comment.