Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add in_shape, defualt kwarg at WorkSheet::get method for resolving issue #947, #985

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions gspread/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def a1_to_rowcol(label):

col = 0
for i, c in enumerate(reversed(column_label)):
col += (ord(c) - MAGIC_NUMBER) * (26 ** i)
col += (ord(c) - MAGIC_NUMBER) * (26**i)
else:
raise IncorrectCellLabel(label)

Expand Down Expand Up @@ -302,7 +302,7 @@ def _a1_to_rowcol_unbounded(label):
if column_label:
col = 0
for i, c in enumerate(reversed(column_label.upper())):
col += (ord(c) - MAGIC_NUMBER) * (26 ** i)
col += (ord(c) - MAGIC_NUMBER) * (26**i)
else:
col = inf

Expand Down Expand Up @@ -505,6 +505,12 @@ def absolute_range_name(sheet_name, range_name=None):
return sheet_name


def absolute_range_to_grid_range(range_name):
_, a1_range = range_name.split("!")

return a1_range_to_grid_range(a1_range)


def is_scalar(x):
"""Return True if the value is scalar.

Expand Down
81 changes: 80 additions & 1 deletion gspread/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

"""

from itertools import zip_longest

from .cell import Cell
from .exceptions import GSpreadException
from .urls import SPREADSHEET_URL, WORKSHEET_DRIVE_URL
Expand All @@ -16,6 +18,7 @@
a1_range_to_grid_range,
a1_to_rowcol,
absolute_range_name,
absolute_range_to_grid_range,
accepted_kwargs,
cast_to_a1_notation,
cell_list_to_rect,
Expand Down Expand Up @@ -569,6 +572,8 @@ def update_cells(self, cell_list, value_input_option=ValueInputOption.raw):
major_dimension=None,
value_render_option=None,
date_time_render_option=None,
default="",
in_shape=False,
)
def get(self, range_name=None, **kwargs):
"""Reads values of a single range or a cell of a sheet.
Expand All @@ -588,6 +593,14 @@ def get(self, range_name=None, **kwargs):
``value_render_option`` is ``ValueRenderOption.formatted``. The default
``date_time_render_option`` is ``SERIAL_NUMBER``.

:param bool in_shape: (optional) How Values in list(2D array) are returned.
Default option is same as before, it will return different length of list
depending on items are actually in given range.
If param ``range_name`` is ``None``, it will throw Exception

:param Any default: (optional) How empty element will be filled with.
Default is empty string.

Examples::

# Return all values from the sheet
Expand All @@ -602,8 +615,25 @@ def get(self, range_name=None, **kwargs):
# Return values of 'my_range' named range
worksheet.get('my_range')

# Return value of 'A1:D2' range with `in_shape` option
worksheet.get('my_range')
>> [ ['', '1b'], ['2a', '2b', '2c'] ]
worksheet.get('my_range', in_shape = True)
>> [ ['', '1b', '', ''], ['2a', '2b', '2c', ''] ]

# Return value of 'A1:D2' range with `default` option
worksheet.get('my_range', default=None)
>> [ [None, '1b'], ['2a', '2b', '2c'] ]
worksheet.get('my_range', in_shape = True, default=None)
>> [ [None, '1b', None, None], ['2a', '2b', '2c', None] ]

.. versionadded:: 3.3
"""
if range_name is None and kwargs["in_shape"] is False:
raise Exception(
"`in_shape` keyword option should be used with `range` option, not a whole sheet"
)

range_name = absolute_range_name(self.title, range_name)

params = filter_dict_values(
Expand All @@ -616,7 +646,11 @@ def get(self, range_name=None, **kwargs):

response = self.spreadsheet.values_get(range_name, params=params)

return ValueRange.from_json(response)
inflated_response = self._inflate(
response, kwargs["in_shape"], kwargs["default"]
)

return ValueRange.from_json(inflated_response)

@accepted_kwargs(
major_dimension=None,
Expand Down Expand Up @@ -662,6 +696,51 @@ def batch_get(self, ranges, **kwargs):

return [ValueRange.from_json(x) for x in response["valueRanges"]]

def _inflate(self, response, in_shape=False, default=""):
"""
Used for filling empty cells before response from Google Spreadsheets is
wrapped by `ValueRange`. This is not intended to be directly used by users.

:param dict ranges: response data from `Spreadsheet::values_get`

:param bool in_shape: (optional) whether to enlarge response value to make lists as same lengths

:param Any default: (optional) default value to fill in empty cell, default
"""

if not in_shape and default == "":
return response

grid_range = absolute_range_to_grid_range(response["range"])

row_length = grid_range["endRowIndex"] - grid_range["startRowIndex"]
col_length = grid_range["endColumnIndex"] - grid_range["startColumnIndex"]

template = []

if in_shape:

if response["majorDimension"] == Dimension.cols:
row_length, col_length = col_length, row_length

for row, _ in zip_longest(response["values"], range(row_length)):
template.append(
[
value
for value, _ in zip_longest(
row, range(col_length), fillvalue=default
)
]
)

if not in_shape:
for array in response["values"]:
template.append([value if value != "" else default for value in array])

response["values"] = template

return response

@accepted_kwargs(
raw=True,
major_dimension=None,
Expand Down
50 changes: 50 additions & 0 deletions tests/worksheet_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,3 +891,53 @@ def test_batch_clear(self):
# make sure cells are empty
self.assertListEqual(w.get_values("A1:B1"), [])
self.assertListEqual(w.get_values("C2:E2"), [])

@pytest.mark.vcr()
def test_get_values_in_shape_with_default_value(self):
values = [
["A1", "B1", "", "D1"],
["", "b2", "", ""],
["", "", "", ""],
["A4", "B4", "", ""],
]

self.sheet.update("A1", values)

read_data = self.sheet.get(
"A1:D4", major_dimension=utils.Dimension.rows, in_shape=True
)

self.assertEqual(read_data, values)

read_data = self.sheet.get(
"A1:D4", major_dimension=utils.Dimension.cols, in_shape=True
)

self.assertEqual(
read_data,
[
["A1", "", "", "A4"],
["B1", "b2", "", "B4"],
["", "", "", ""],
["D1", "", "", ""],
],
)

read_data = self.sheet.get("A1:D4", default="#")

self.assertEqual(
read_data,
[["A1", "B1", "#", "D1"], ["#", "b2"], [], ["A4", "B4", "#", "D4"]],
)

read_data = self.sheet.get("A1:D4", in_shape=True, default="#")

self.assertEqual(
read_data,
[
["A1", "B1", "#", "D1"],
["#", "b2", "#", "#"],
["#", "#", "#", "#"],
["A4", "B4", "#", "D4"],
],
)