Skip to content

Commit f7307a7

Browse files
authored
Merge branch 'master' into cgkoutzigiannis-change-tab-color
2 parents 4ed531c + 912a1b4 commit f7307a7

13 files changed

+360
-100
lines changed

HISTORY.rst

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

4+
* Add `client_factory` param to `auth` methods by @jlumbroso in https://github.com/burnash/gspread/pull/1075
5+
46
5.4.0 (2022-06-01)
57
------------------
68
* fix typo by @joswlv in https://github.com/burnash/gspread/pull/1031

docs/api/exceptions.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Exceptions
66
.. autoexception:: gspread.exceptions.CellNotFound
77
.. autoexception:: gspread.exceptions.GSpreadException
88
.. autoexception:: gspread.exceptions.IncorrectCellLabel
9+
.. autoexception:: gspread.exceptions.InvalidInputValue
910
.. autoexception:: gspread.exceptions.NoValidUrlKeyFound
1011
.. autoexception:: gspread.exceptions.SpreadsheetNotFound
1112
.. autoexception:: gspread.exceptions.UnSupportedExportFormat

docs/oauth2.rst

+6-8
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,12 @@ manager.
188188
import gspread
189189

190190
credentials = {
191-
{
192-
"installed": {
193-
"client_id": "12345678901234567890abcdefghijklmn.apps.googleusercontent.com",
194-
"project_id": "my-project1234",
195-
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
196-
"token_uri": "https://oauth2.googleapis.com/token",
197-
...
198-
}
191+
"installed": {
192+
"client_id": "12345678901234567890abcdefghijklmn.apps.googleusercontent.com",
193+
"project_id": "my-project1234",
194+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
195+
"token_uri": "https://oauth2.googleapis.com/token",
196+
...
199197
}
200198
}
201199
gc, authorized_user = gspread.oauth_from_dict(credentials)

gspread/__init__.py

+8-15
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@
1313
__author__ = "Anton Burnashev"
1414

1515

16-
from .auth import oauth, oauth_from_dict, service_account, service_account_from_dict
16+
from .auth import (
17+
authorize,
18+
oauth,
19+
oauth_from_dict,
20+
service_account,
21+
service_account_from_dict,
22+
)
1723
from .cell import Cell
18-
from .client import Client
24+
from .client import BackoffClient, Client, ClientFactory
1925
from .exceptions import (
2026
CellNotFound,
2127
GSpreadException,
@@ -26,16 +32,3 @@
2632
)
2733
from .spreadsheet import Spreadsheet
2834
from .worksheet import Worksheet
29-
30-
31-
def authorize(credentials, client_class=Client):
32-
"""Login to Google API using OAuth2 credentials.
33-
This is a shortcut function which
34-
instantiates `client_class`.
35-
By default :class:`gspread.Client` is used.
36-
37-
:returns: `client_class` instance.
38-
"""
39-
40-
client = client_class(auth=credentials)
41-
return client

gspread/auth.py

+46-10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ def get_config_dir(config_dir_name="gspread", os_is_windows=os.name == "nt"):
4949
DEFAULT_SERVICE_ACCOUNT_FILENAME = DEFAULT_CONFIG_DIR / "service_account.json"
5050

5151

52+
def authorize(credentials, client_factory=Client):
53+
"""Login to Google API using OAuth2 credentials.
54+
This is a shortcut/helper function which
55+
instantiates a client using `client_factory`.
56+
By default :class:`gspread.Client` is used (but could also use
57+
:class:`gspread.BackoffClient` to avoid rate limiting).
58+
59+
:returns: An instance of the class produced by `client_factory`.
60+
:rtype: :class:`gspread.client.Client`
61+
"""
62+
63+
return client_factory(auth=credentials)
64+
65+
5266
def local_server_flow(client_config, scopes, port=0):
5367
"""Run an OAuth flow using a local server strategy.
5468
@@ -105,6 +119,7 @@ def oauth(
105119
flow=local_server_flow,
106120
credentials_filename=DEFAULT_CREDENTIALS_FILENAME,
107121
authorized_user_filename=DEFAULT_AUTHORIZED_USER_FILENAME,
122+
client_factory=Client,
108123
):
109124
r"""Authenticate with OAuth Client ID.
110125
@@ -162,8 +177,12 @@ def oauth(
162177
163178
* `%APPDATA%\gspread\authorized_user.json` on Windows
164179
* `~/.config/gspread/authorized_user.json` everywhere else
180+
:type client_factory: :class:`gspread.ClientFactory`
181+
:param client_factory: A factory function that returns a client class.
182+
Defaults to :class:`gspread.Client` (but could also use
183+
:class:`gspread.BackoffClient` to avoid rate limiting)
165184
166-
:rtype: :class:`gspread.Client`
185+
:rtype: :class:`gspread.client.Client`
167186
"""
168187
authorized_user_filename = Path(authorized_user_filename)
169188
creds = load_credentials(filename=authorized_user_filename)
@@ -174,14 +193,15 @@ def oauth(
174193
creds = flow(client_config=client_config, scopes=scopes)
175194
store_credentials(creds, filename=authorized_user_filename)
176195

177-
return Client(auth=creds)
196+
return client_factory(auth=creds)
178197

179198

180199
def oauth_from_dict(
181200
credentials=None,
182201
authorized_user_info=None,
183202
scopes=DEFAULT_SCOPES,
184203
flow=local_server_flow,
204+
client_factory=Client,
185205
):
186206
r"""Authenticate with OAuth Client ID.
187207
@@ -237,8 +257,12 @@ def oauth_from_dict(
237257
:param list scopes: The scopes used to obtain authorization.
238258
:param function flow: OAuth flow to use for authentication.
239259
Defaults to :meth:`~gspread.auth.local_server_flow`
260+
:type client_factory: :class:`gspread.ClientFactory`
261+
:param client_factory: A factory function that returns a client class.
262+
Defaults to :class:`gspread.Client` (but could also use
263+
:class:`gspread.BackoffClient` to avoid rate limiting)
240264
241-
:rtype: tuple(:class:`gspread.Client` , str)
265+
:rtype: (`gspread.client.Client`, str)
242266
"""
243267

244268
creds = None
@@ -248,15 +272,19 @@ def oauth_from_dict(
248272
if not creds:
249273
creds = flow(client_config=credentials, scopes=scopes)
250274

251-
client = Client(auth=creds)
275+
client = client_factory(auth=creds)
252276

253277
# must return the creds to the user
254278
# must strip the token an use the dedicated method from Credentials
255279
# to return a dict "safe to store".
256280
return (client, creds.to_json("token"))
257281

258282

259-
def service_account(filename=DEFAULT_SERVICE_ACCOUNT_FILENAME, scopes=DEFAULT_SCOPES):
283+
def service_account(
284+
filename=DEFAULT_SERVICE_ACCOUNT_FILENAME,
285+
scopes=DEFAULT_SCOPES,
286+
client_factory=Client,
287+
):
260288
"""Authenticate using a service account.
261289
262290
``scopes`` parameter defaults to read/write scope available in
@@ -274,14 +302,18 @@ def service_account(filename=DEFAULT_SERVICE_ACCOUNT_FILENAME, scopes=DEFAULT_SC
274302
275303
:param str filename: The path to the service account json file.
276304
:param list scopes: The scopes used to obtain authorization.
305+
:type client_factory: :class:`gspread.ClientFactory`
306+
:param client_factory: A factory function that returns a client class.
307+
Defaults to :class:`gspread.Client` (but could also use
308+
:class:`gspread.BackoffClient` to avoid rate limiting)
277309
278-
:rtype: :class:`gspread.Client`
310+
:rtype: :class:`gspread.client.Client`
279311
"""
280312
creds = ServiceAccountCredentials.from_service_account_file(filename, scopes=scopes)
281-
return Client(auth=creds)
313+
return client_factory(auth=creds)
282314

283315

284-
def service_account_from_dict(info, scopes=DEFAULT_SCOPES):
316+
def service_account_from_dict(info, scopes=DEFAULT_SCOPES, client_factory=Client):
285317
"""Authenticate using a service account (json).
286318
287319
``scopes`` parameter defaults to read/write scope available in
@@ -299,11 +331,15 @@ def service_account_from_dict(info, scopes=DEFAULT_SCOPES):
299331
300332
:param info (Mapping[str, str]): The service account info in Google format
301333
:param list scopes: The scopes used to obtain authorization.
334+
:type client_factory: :class:`gspread.ClientFactory`
335+
:param client_factory: A factory function that returns a client class.
336+
Defaults to :class:`gspread.Client` (but could also use
337+
:class:`gspread.BackoffClient` to avoid rate limiting)
302338
303-
:rtype: :class:`gspread.Client`
339+
:rtype: :class:`gspread.client.Client`
304340
"""
305341
creds = ServiceAccountCredentials.from_service_account_info(
306342
info=info,
307343
scopes=scopes,
308344
)
309-
return Client(auth=creds)
345+
return client_factory(auth=creds)

gspread/client.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
from http import HTTPStatus
11+
from typing import Type
1112

1213
from google.auth.transport.requests import AuthorizedSession
1314

@@ -57,6 +58,12 @@ def login(self):
5758
self.session.headers.update({"Authorization": "Bearer %s" % self.auth.token})
5859

5960
def set_timeout(self, timeout):
61+
"""How long to wait for the server to send
62+
data before giving up, as a float, or a ``(connect timeout,
63+
read timeout)`` tuple.
64+
65+
Value for ``timeout`` is in seconds (s).
66+
"""
6067
self.timeout = timeout
6168

6269
def request(
@@ -223,9 +230,9 @@ def create(self, title, folder_id=None):
223230
return self.open_by_key(spreadsheet_id)
224231

225232
def export(self, file_id, format=ExportFormat.PDF):
226-
"""Export the spreadsheet in the format.
233+
"""Export the spreadsheet in the given format.
227234
228-
:param str file_id: A key of a spreadsheet to export
235+
:param str file_id: The key of the spreadsheet to export
229236
230237
:param str format: The format of the resulting file.
231238
Possible values are:
@@ -239,7 +246,7 @@ def export(self, file_id, format=ExportFormat.PDF):
239246
240247
See `ExportFormat`_ in the Drive API.
241248
242-
:returns bytes: A content of the exported file.
249+
:returns bytes: The content of the exported file.
243250
244251
.. _ExportFormat: https://developers.google.com/drive/api/guides/ref-export-formats
245252
"""
@@ -459,6 +466,8 @@ def insert_permission(
459466
:param bool with_link: (optional) Whether the link is required for this
460467
permission to be active.
461468
469+
:returns dict: the newly created permission
470+
462471
Examples::
463472
464473
# Give write permissions to [email protected]
@@ -496,7 +505,7 @@ def insert_permission(
496505
"supportsAllDrives": "true",
497506
}
498507

499-
self.request("post", url, json=payload, params=params)
508+
return self.request("post", url, json=payload, params=params)
500509

501510
def remove_permission(self, file_id, permission_id):
502511
"""Deletes a permission from a file.
@@ -526,6 +535,11 @@ class BackoffClient(Client):
526535
This Client is not production ready yet.
527536
Use it at your own risk !
528537
538+
.. note::
539+
To use with the `auth` module, make sure to pass this backoff
540+
client factory using the ``client_factory`` parameter of the
541+
method used.
542+
529543
.. note::
530544
Currently known issues are:
531545
@@ -573,3 +587,6 @@ def request(self, *args, **kwargs):
573587

574588
# failed too many times, raise APIEerror
575589
raise err
590+
591+
592+
ClientFactory = Type[Client]

gspread/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class IncorrectCellLabel(GSpreadException):
3535
"""The cell label is incorrect."""
3636

3737

38+
class InvalidInputValue(GSpreadException):
39+
"""The provided values is incorrect."""
40+
41+
3842
class APIError(GSpreadException):
3943
def __init__(self, response):
4044

0 commit comments

Comments
 (0)