Skip to content

Commit

Permalink
Merge pull request #1305 from burnash/feature/maintain-input-size
Browse files Browse the repository at this point in the history
Add `maintain_size` to keep asked for size in `get`, `get_values`
  • Loading branch information
lavigne958 authored Oct 21, 2023
2 parents 0a2f084 + 5882c0c commit 2991e46
Show file tree
Hide file tree
Showing 7 changed files with 2,515 additions and 0 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ values_list = worksheet.col_values(1)
list_of_lists = worksheet.get_values()
```

### Getting a range of values

Receive only the cells with a value in them
```python
>>> worksheet.get_values("A1:B4")
[['A1', 'B1'], ['A2']]
```

Receive a lists of lists matching the requested size
regardless if values are empty or not

```python
>>> worksheet.get_values("A1:B4", maintain_size=True)
[['A1', 'B1'], ['A2', ''], ['', ''], ['', '']]
```

### Finding a Cell

```python
Expand Down
39 changes: 39 additions & 0 deletions gspread/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
MAGIC_NUMBER = 64
CELL_ADDR_RE = re.compile(r"([A-Za-z]+)([1-9]\d*)")
A1_ADDR_ROW_COL_RE = re.compile(r"([A-Za-z]+)?([1-9]\d*)?$")
A1_ADDR_FULL_RE = re.compile(r"[A-Za-z]+\d+:[A-Za-z]+\d+") # e.g. A1:B2 not A1:B

URL_KEY_V1_RE = re.compile(r"key=([^&#]+)")
URL_KEY_V2_RE = re.compile(r"/spreadsheets/d/([a-zA-Z0-9-_]+)")
Expand Down Expand Up @@ -871,6 +872,44 @@ def to_hex(value: float) -> str:
return f"#{to_hex(red)}{to_hex(green)}{to_hex(blue)}"


def is_full_a1_notation(range_name: str) -> bool:
"""Check if the range name is a full A1 notation.
"A1:B2", "Sheet1!A1:B2" are full A1 notations
"A1:B", "A1" are not
Args:
range_name (str): The range name to check.
Returns:
bool: True if the range name is a full A1 notation, False otherwise.
Examples:
>>> is_full_a1_notation("A1:B2")
True
>>> is_full_a1_notation("A1:B")
False
"""
return A1_ADDR_FULL_RE.search(range_name) is not None


def get_a1_from_absolute_range(range_name: str) -> str:
"""Get the A1 notation from an absolute range name.
"Sheet1!A1:B2" -> "A1:B2"
"A1:B2" -> "A1:B2"
Args:
range_name (str): The range name to check.
Returns:
str: The A1 notation of the range name stripped of the sheet.
"""
if "!" in range_name:
return range_name.split("!")[1]
return range_name


def deprecation_warning(version: str, msg: str) -> None:
"""Emit a deprecation warning.
Expand Down
56 changes: 56 additions & 0 deletions gspread/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
fill_gaps,
filter_dict_values,
finditem,
get_a1_from_absolute_range,
is_full_a1_notation,
is_scalar,
numericise_all,
rowcol_to_a1,
Expand Down Expand Up @@ -364,6 +366,7 @@ def range(self, name=""):
combine_merged_cells=False,
value_render_option=None,
date_time_render_option=None,
maintain_size=False,
)
def get_values(self, range_name=None, combine_merged_cells=False, **kwargs):
"""Returns a list of lists containing all values from specified range.
Expand Down Expand Up @@ -441,6 +444,25 @@ def get_values(self, range_name=None, combine_merged_cells=False, **kwargs):
Empty trailing rows and columns will not be included.
:param bool maintain_size: (optional) Returns a matrix of values matching the size of the requested range.
.. warning::
This can only work if the requested range is a complete bounded A1 notation.
Example: ``A1:D4``: OK, ``C3:F``: Not OK, we don't know the end size of the requested range.
This does not work with ``named_range`` either.
Examples::
# Works
>>> worksheet.get("A1:B2", maintain_size=True)
[['A1', 'B1'], ['A2', '']]
# Does NOT maintain the requested size
>>> worksheet.get("A1:B", maintain_size=True)
[['A1', 'B1'], ['A2'], [], ['A4', 'B4'], ['A5']]
Examples::
# Return all values from the sheet
Expand Down Expand Up @@ -896,6 +918,7 @@ def update_cells(self, cell_list, value_input_option=ValueInputOption.raw):
major_dimension=None,
value_render_option=None,
date_time_render_option=None,
maintain_size=False,
)
def get(self, range_name=None, **kwargs):
"""Reads values of a single range or a cell of a sheet.
Expand Down Expand Up @@ -951,6 +974,26 @@ def get(self, range_name=None, **kwargs):
This is ignored if ``value_render_option`` is ``ValueRenderOption.formatted``.
The default ``date_time_render_option`` is ``DateTimeOption.serial_number``.
:param bool maintain_size: (optional) Returns a matrix of values matching the size of the requested range.
.. warning::
This can only work if the requested range is a complete bounded A1 notation.
Example: ``A1:D4``: OK, ``C3:F``: Not OK, we don't know the end size of the requested range.
This does not work with ``named_range`` either.
Examples::
# Works
>>> worksheet.get("A1:B2", maintain_size=True)
[['A1', 'B1'], ['A2', '']]
# Does NOT maintain the requested size
>>> worksheet.get("A1:B", maintain_size=True)
[['A1', 'B1'], ['A2'], [], ['A4', 'B4'], ['A5']]
:type date_time_render_option: :namedtuple:`~gspread.utils.DateTimeOption`
:rtype: :class:`gspread.worksheet.ValueRange`
Expand Down Expand Up @@ -983,6 +1026,19 @@ def get(self, range_name=None, **kwargs):

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

values = response.get("values", [])

# range_name must be a full grid range so that we can guarantee
# startRowIndex and endRowIndex properties
if kwargs["maintain_size"] is True and is_full_a1_notation(range_name):
a1_range = get_a1_from_absolute_range(range_name)
grid_range = a1_range_to_grid_range(a1_range)
rows = grid_range["endRowIndex"] - grid_range["startRowIndex"]
cols = grid_range["endColumnIndex"] - grid_range["startColumnIndex"]
values = fill_gaps(values, rows=rows, cols=cols)

response["values"] = values

return ValueRange.from_json(response)

@accepted_kwargs(
Expand Down
Loading

0 comments on commit 2991e46

Please sign in to comment.