Skip to content

Commit 8cfc93b

Browse files
authored
Merge branch 'burnash:master' into master
2 parents da5c5a3 + 27f4804 commit 8cfc93b

14 files changed

+3426
-271
lines changed

.github/workflows/release.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
user: __token__
4040
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
4141
repository-url: https://test.pypi.org/legacy/
42+
verbose: true
4243
- name: Publish to PyPi
4344
uses: pypa/gh-action-pypi-publish@release/v1
4445
with:

HISTORY.rst

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
11
Release History
22
===============
33

4+
6.2.0 (2025-02-27)
5+
------------------
6+
7+
* Add property expiry in gspread client by @lavigne958 in https://github.com/burnash/gspread/pull/1453
8+
* Bump typing-extensions from 4.11.0 to 4.12.0 by @dependabot in https://github.com/burnash/gspread/pull/1471
9+
* Fix code block formatting typo in README by @agrvz in https://github.com/burnash/gspread/pull/1474
10+
* ignore jinja CVE by @lavigne958 in https://github.com/burnash/gspread/pull/1481
11+
* Type part of test suite utils by @lavigne958 in https://github.com/burnash/gspread/pull/1483
12+
* Remove passing exception as args to super in APIError by @mike-flowers-airbnb in https://github.com/burnash/gspread/pull/1477
13+
* Bump mypy from 1.10.0 to 1.10.1 by @dependabot in https://github.com/burnash/gspread/pull/1488
14+
* Update advanced.rst by @yatender-rjliving in https://github.com/burnash/gspread/pull/1492
15+
* Bump bandit from 1.7.8 to 1.7.9 by @dependabot in https://github.com/burnash/gspread/pull/1485
16+
* Bump flake8 from 7.0.0 to 7.1.0 by @dependabot in https://github.com/burnash/gspread/pull/1486
17+
* Bump typing-extensions from 4.12.0 to 4.12.2 by @dependabot in https://github.com/burnash/gspread/pull/1480
18+
* Bump mypy from 1.10.1 to 1.11.1 by @dependabot in https://github.com/burnash/gspread/pull/1497
19+
* Bump black from 24.4.2 to 24.8.0 by @dependabot in https://github.com/burnash/gspread/pull/1499
20+
* Bump flake8 from 7.1.0 to 7.1.1 by @dependabot in https://github.com/burnash/gspread/pull/1501
21+
* Fix docstring about BackOffHTTPClient by @pataiji in https://github.com/burnash/gspread/pull/1502
22+
* Fix comment to reflect correct google-auth package version requirement by @ikmals in https://github.com/burnash/gspread/pull/1503
23+
* Doc/community addons orm package by @lavigne958 in https://github.com/burnash/gspread/pull/1506
24+
* fix: fix type annotation for default_blank by @hiro-o918 in https://github.com/burnash/gspread/pull/1505
25+
* Bump mypy from 1.11.1 to 1.11.2 by @dependabot in https://github.com/burnash/gspread/pull/1508
26+
* better handler API error parsing. by @lavigne958 in https://github.com/burnash/gspread/pull/1510
27+
* Add test on receiving an invalid JSON in the APIError exception handler. by @lavigne958 in https://github.com/burnash/gspread/pull/1512
28+
* [feature] Add 'expand_table' feature by @lavigne958 in https://github.com/burnash/gspread/pull/1475
29+
* Bump bandit from 1.7.9 to 1.7.10 by @dependabot in https://github.com/burnash/gspread/pull/1514
30+
* Created a `batch_merge` function [Issue #1473] by @muddi900 in https://github.com/burnash/gspread/pull/1498
31+
* Added a range option to `Worksheet.get_notes` [Issue #1482] by @muddi900 in https://github.com/burnash/gspread/pull/1487
32+
* Documentation update for gspread.worksheet.Worksheet.get_all_records by @levon003 in https://github.com/burnash/gspread/pull/1529
33+
* add example for `batch_merge` by @alifeee in https://github.com/burnash/gspread/pull/1542
34+
* explicitly list exported package symbols by @alinsavix in https://github.com/burnash/gspread/pull/1531
35+
36+
6.1.4 (2024-10-21)
37+
------------------
38+
39+
* remove dependency on requests-2.27.0
40+
41+
6.1.3 (2024-10-03)
42+
------------------
43+
44+
* ignore jinja CVE by @lavigne958 in https://github.com/burnash/gspread/pull/1481
45+
* Remove passing exception as args to super in APIError by @mike-flowers-airbnb in https://github.com/burnash/gspread/pull/1477
46+
* better handler API error parsing. by @lavigne958 in https://github.com/burnash/gspread/pull/1510
47+
* Add test on receiving an invalid JSON in the APIError exception handler. by @lavigne958 in https://github.com/burnash/gspread/pull/1512
48+
449
6.1.2 (2024-05-17)
550
------------------
651

@@ -63,7 +108,7 @@ Release History
63108

64109
6.0.0 (2024-01-28)
65110
------------------
66-
111+
New Contributor
67112
* Remove deprecated method delete_row by @cgkoutzigiannis in https://github.com/burnash/gspread/pull/1062
68113
* Initial typing in client.py by @OskarBrzeski in https://github.com/burnash/gspread/pull/1159
69114
* Split client http client by @lavigne958 in https://github.com/burnash/gspread/pull/1190

docs/user-guide.rst

+67-1
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,73 @@ Check out the api docs for `DataValidationRule`_ and `CondtionType`_ for more de
350350

351351
.. _CondtionType: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionType
352352

353-
.. _DataValidationRule: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#DataValidationRule
353+
.. _DataValidationRule: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#DataValidationRule
354+
355+
Extract table
356+
~~~~~~~~~~~~~
357+
358+
Gspread provides a function to extract a data table.
359+
A data table is defined as a rectangular table that stops either on the **first empty** cell or
360+
the **enge of the sheet**.
361+
362+
You can extract table from any address by providing the top left corner of the desired table.
363+
364+
Gspread provides 3 directions for searching the end of the table:
365+
366+
* :attr:`~gspread.utils.TableDirection.right`: extract a single row searching on the right of the starting cell
367+
* :attr:`~gspread.utils.TableDirection.down`: extract a single column searching on the bottom of the starting cell
368+
* :attr:`~gspread.utils.TableDirection.table`: extract a rectangular table by first searching right from starting cell,
369+
then searching down from starting cell.
370+
371+
.. note::
372+
373+
Gspread will not look for empty cell inside the table. it only look at the top row and first column.
374+
375+
Example extracting a table from the below sample sheet:
376+
377+
.. list-table:: Find table
378+
:header-rows: 1
379+
380+
* - ID
381+
- Name
382+
- Universe
383+
- Super power
384+
* - 1
385+
- Batman
386+
- DC
387+
- Very rich
388+
* - 2
389+
- DeadPool
390+
- Marvel
391+
- self healing
392+
* - 3
393+
- Superman
394+
- DC
395+
- super human
396+
* -
397+
- \-
398+
- \-
399+
- \-
400+
* - 5
401+
- Lavigne958
402+
-
403+
- maintains Gspread
404+
* - 6
405+
- Alifee
406+
-
407+
- maintains Gspread
408+
409+
Using the below code will result in rows 2 to 4:
410+
411+
.. code:: python
412+
413+
worksheet.expand("A2")
414+
415+
[
416+
["Batman", "DC", "Very rich"],
417+
["DeadPool", "Marvel", "self healing"],
418+
["Superman", "DC", "super human"],
419+
]
354420
355421
356422

gspread/__init__.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Google Spreadsheets Python API"""
22

3-
__version__ = "6.1.2"
3+
__version__ = "6.2.0"
44
__author__ = "Anton Burnashev"
55

66

@@ -24,3 +24,44 @@
2424
from .http_client import BackOffHTTPClient, HTTPClient
2525
from .spreadsheet import Spreadsheet
2626
from .worksheet import ValueRange, Worksheet
27+
28+
from . import urls as urls
29+
from . import utils as utils
30+
31+
__all__ = [
32+
# from .auth
33+
"api_key",
34+
"authorize",
35+
"oauth",
36+
"oauth_from_dict",
37+
"service_account",
38+
"service_account_from_dict",
39+
40+
# from .cell
41+
"Cell",
42+
43+
# from .client
44+
"Client",
45+
46+
# from .http_client
47+
"BackOffHTTPClient",
48+
"HTTPClient",
49+
50+
# from .spreadsheet
51+
"Spreadsheet",
52+
53+
# from .worksheet
54+
"Worksheet",
55+
"ValueRange",
56+
57+
# from .exceptions
58+
"GSpreadException",
59+
"IncorrectCellLabel",
60+
"NoValidUrlKeyFound",
61+
"SpreadsheetNotFound",
62+
"WorksheetNotFound",
63+
64+
# full module imports
65+
"urls",
66+
"utils",
67+
]

gspread/exceptions.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from typing import Any, Mapping
1010

1111
from requests import Response
12-
from requests.exceptions import JSONDecodeError
1312

1413

1514
class UnSupportedExportFormat(Exception):
@@ -43,15 +42,15 @@ class APIError(GSpreadException):
4342
def __init__(self, response: Response):
4443
try:
4544
error = response.json()["error"]
46-
except JSONDecodeError:
45+
except Exception as e:
4746
# in case we failed to parse the error from the API
4847
# build an empty error object to notify the caller
4948
# and keep the exception raise flow running
5049

5150
error = {
5251
"code": -1,
5352
"message": response.text,
54-
"status": "invalid JSON",
53+
"status": "invalid JSON: '{}'".format(e),
5554
}
5655

5756
super().__init__(error)

gspread/utils.py

+129
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ class ValidationConditionType(StrEnum):
168168
filter_expression = "FILTER_EXPRESSION"
169169

170170

171+
class TableDirection(StrEnum):
172+
table = "TABLE"
173+
down = "DOWN"
174+
right = "RIGHT"
175+
176+
171177
def convert_credentials(credentials: Credentials) -> Credentials:
172178
module = credentials.__module__
173179
cls = credentials.__class__.__name__
@@ -979,6 +985,129 @@ def to_records(
979985
return [dict(zip(headers, row)) for row in values]
980986

981987

988+
def _expand_right(values: List[List[str]], start: int, end: int, row: int) -> int:
989+
"""This is a private function, returning the column index of the last non empty cell
990+
on the given row.
991+
992+
Search starts from ``start`` index column.
993+
Search ends on ``end`` index column.
994+
Searches only in the row pointed by ``row``.
995+
"""
996+
try:
997+
return values[row].index("", start, end) - 1
998+
except ValueError:
999+
return end
1000+
1001+
1002+
def _expand_bottom(values: List[List[str]], start: int, end: int, col: int) -> int:
1003+
"""This is a private function, returning the row index of the last non empty cell
1004+
on the given column.
1005+
1006+
Search starts from ``start`` index row.
1007+
Search ends on ``end`` index row.
1008+
Searches only in the column pointed by ``col``.
1009+
"""
1010+
for rows in range(start, end):
1011+
# in case we try to look further than last row
1012+
if rows >= len(values):
1013+
return len(values) - 1
1014+
1015+
# check if cell is empty (or the row => empty cell)
1016+
if col >= len(values[rows]) or values[rows][col] == "":
1017+
return rows - 1
1018+
1019+
return end - 1
1020+
1021+
1022+
def find_table(
1023+
values: List[List[str]],
1024+
start_range: str,
1025+
direction: TableDirection = TableDirection.table,
1026+
) -> List[List[str]]:
1027+
"""Expands a list of values based on non-null adjacent cells.
1028+
1029+
Expand can be done in 3 directions defined in :class:`~gspread.utils.TableDirection`
1030+
1031+
* ``TableDirection.right``: expands right until the first empty cell
1032+
* ``TableDirection.down``: expands down until the first empty cell
1033+
* ``TableDirection.table``: expands right until the first empty cell and down until first empty cell
1034+
1035+
In case of empty result an empty list is restuned.
1036+
1037+
When the given ``start_range`` is outside the given matrix of values the exception
1038+
:class:`~gspread.exceptions.InvalidInputValue` is raised.
1039+
1040+
Example::
1041+
1042+
values = [
1043+
['', '', '', '', '' ],
1044+
['', 'B2', 'C2', '', 'E2'],
1045+
['', 'B3', 'C3', '', 'E3'],
1046+
['', '' , '' , '', 'E4'],
1047+
]
1048+
>>> utils.find_table(TableDirection.table, 'B2')
1049+
[
1050+
['B2', 'C2'],
1051+
['B3', 'C3'],
1052+
]
1053+
1054+
1055+
.. note::
1056+
1057+
the ``TableDirection.table`` will look right from starting cell then look down from starting cell.
1058+
It will not check cells located inside the table. This could lead to
1059+
potential empty values located in the middle of the table.
1060+
1061+
.. warning::
1062+
1063+
Given values must be padded with `''` empty values.
1064+
1065+
:param list[list] values: values where to find the table.
1066+
:param gspread.utils.TableDirection direction: the expand direction.
1067+
:param str start_range: the starting cell range.
1068+
:rtype list(list): the resulting matrix
1069+
"""
1070+
row, col = a1_to_rowcol(start_range)
1071+
1072+
# a1_to_rowcol returns coordinates starting form 1
1073+
row -= 1
1074+
col -= 1
1075+
1076+
if row >= len(values):
1077+
raise InvalidInputValue(
1078+
"given row for start_range is outside given values: start range row ({}) >= rows in values {}".format(
1079+
row, len(values)
1080+
)
1081+
)
1082+
1083+
if col >= len(values[row]):
1084+
raise InvalidInputValue(
1085+
"given column for start_range is outside given values: start range column ({}) >= columns in values {}".format(
1086+
col, len(values[row])
1087+
)
1088+
)
1089+
1090+
if direction == TableDirection.down:
1091+
rightMost = col
1092+
bottomMost = _expand_bottom(values, row, len(values), col)
1093+
1094+
if direction == TableDirection.right:
1095+
bottomMost = row
1096+
rightMost = _expand_right(values, col, len(values[row]), row)
1097+
1098+
if direction == TableDirection.table:
1099+
rightMost = _expand_right(values, col, len(values[row]), row)
1100+
bottomMost = _expand_bottom(values, row, len(values), col)
1101+
1102+
result = []
1103+
1104+
# build resulting array
1105+
for rows in values[row : bottomMost + 1]:
1106+
result.append(rows[col : rightMost + 1])
1107+
1108+
return result
1109+
1110+
9821111
# SHOULD NOT BE NEEDED UNTIL NEXT MAJOR VERSION
9831112
# DEPRECATION_WARNING_TEMPLATE = (
9841113
# "[Deprecated][in version {v_deprecated}]: {msg_deprecated}"

0 commit comments

Comments
 (0)