@@ -162,6 +162,72 @@ class TableCreationError(ValueError):
162162 pass
163163
164164
165+ class Context (object ):
166+ """Storage for objects to be used throughout a session.
167+
168+ A Context object is initialized when the ``pandas_gbq`` module is
169+ imported, and can be found at :attr:`pandas_gbq.context`.
170+ """
171+
172+ def __init__ (self ):
173+ self ._credentials = None
174+ self ._project = None
175+
176+ @property
177+ def credentials (self ):
178+ """google.auth.credentials.Credentials: Credentials to use for Google
179+ APIs.
180+
181+ Note:
182+ These credentials are automatically cached in memory by calls to
183+ :func:`pandas_gbq.read_gbq` and :func:`pandas_gbq.to_gbq`. To
184+ manually set the credentials, construct an
185+ :class:`google.auth.credentials.Credentials` object and set it as
186+ the context credentials as demonstrated in the example below. See
187+ `auth docs`_ for more information on obtaining credentials.
188+
189+ Example:
190+ Manually setting the context credentials:
191+ >>> import pandas_gbq
192+ >>> from google.oauth2 import service_account
193+ >>> credentials = (service_account
194+ ... .Credentials.from_service_account_file(
195+ ... '/path/to/key.json'))
196+ >>> pandas_gbq.context.credentials = credentials
197+ .. _auth docs: http://google-auth.readthedocs.io
198+ /en/latest/user-guide.html#obtaining-credentials
199+ """
200+ return self ._credentials
201+
202+ @credentials .setter
203+ def credentials (self , value ):
204+ self ._credentials = value
205+
206+ @property
207+ def project (self ):
208+ """str: Default project to use for calls to Google APIs.
209+
210+ Example:
211+ Manually setting the context project:
212+ >>> import pandas_gbq
213+ >>> pandas_gbq.context.project = 'my-project'
214+ """
215+ return self ._project
216+
217+ @project .setter
218+ def project (self , value ):
219+ self ._project = value
220+
221+
222+ # Create an empty context, used to cache credentials.
223+ context = Context ()
224+ """A :class:`pandas_gbq.Context` object used to cache credentials.
225+
226+ Credentials automatically are cached in-memory by :func:`pandas_gbq.read_gbq`
227+ and :func:`pandas_gbq.to_gbq`.
228+ """
229+
230+
165231class GbqConnector (object ):
166232 def __init__ (
167233 self ,
@@ -173,6 +239,7 @@ def __init__(
173239 location = None ,
174240 try_credentials = None ,
175241 ):
242+ global context
176243 from google .api_core .exceptions import GoogleAPIError
177244 from google .api_core .exceptions import ClientError
178245 from pandas_gbq import auth
@@ -185,13 +252,20 @@ def __init__(
185252 self .auth_local_webserver = auth_local_webserver
186253 self .dialect = dialect
187254 self .credentials_path = _get_credentials_file ()
188- self .credentials , default_project = auth .get_credentials (
189- private_key = private_key ,
190- project_id = project_id ,
191- reauth = reauth ,
192- auth_local_webserver = auth_local_webserver ,
193- try_credentials = try_credentials ,
194- )
255+
256+ # Load credentials from cache.
257+ self .credentials = context .credentials
258+ default_project = context .project
259+
260+ # Credentials were explicitly asked for, so don't use the cache.
261+ if private_key or reauth or not self .credentials :
262+ self .credentials , default_project = auth .get_credentials (
263+ private_key = private_key ,
264+ project_id = project_id ,
265+ reauth = reauth ,
266+ auth_local_webserver = auth_local_webserver ,
267+ try_credentials = try_credentials ,
268+ )
195269
196270 if self .project_id is None :
197271 self .project_id = default_project
@@ -201,6 +275,12 @@ def __init__(
201275 "Could not determine project ID and one was not supplied."
202276 )
203277
278+ # Cache the credentials if they haven't been set yet.
279+ if context .credentials is None :
280+ context .credentials = self .credentials
281+ if context .project is None :
282+ context .project = self .project_id
283+
204284 self .client = self .get_client ()
205285
206286 # BQ Queries costs $5 per TB. First 1 TB per month is free
0 commit comments