Skip to content

Commit 320ddd8

Browse files
tswastJon Wayne Parrott
authored and
Jon Wayne Parrott
committed
BigQuery: user credentials to run a query. (#925)
* BigQuery: user credentials to run a query. * BigQuery user creds sample: add tests. Mocks out user credentials using the Application Default Credentials, but uses the same scopes.
1 parent c4912ea commit 320ddd8

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
google-cloud-bigquery==0.24.0
2+
google-auth-oauthlib==0.0.1
23
pytz==2017.2
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2017 Google Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Command-line application to run a query using user credentials.
18+
19+
You must supply a client secrets file, which would normally be bundled with
20+
your application.
21+
"""
22+
23+
import argparse
24+
import time
25+
import uuid
26+
27+
from google.cloud import bigquery
28+
from google_auth_oauthlib import flow
29+
30+
31+
def wait_for_job(job):
32+
while True:
33+
job.reload() # Refreshes the state via a GET request.
34+
if job.state == 'DONE':
35+
if job.error_result:
36+
raise RuntimeError(job.errors)
37+
return
38+
time.sleep(1)
39+
40+
41+
def run_query(credentials, project, query):
42+
client = bigquery.Client(project=project, credentials=credentials)
43+
query_job = client.run_async_query(str(uuid.uuid4()), query)
44+
query_job.use_legacy_sql = False
45+
query_job.begin()
46+
47+
wait_for_job(query_job)
48+
49+
# Drain the query results by requesting a page at a time.
50+
query_results = query_job.results()
51+
page_token = None
52+
53+
while True:
54+
rows, total_rows, page_token = query_results.fetch_data(
55+
max_results=10,
56+
page_token=page_token)
57+
58+
for row in rows:
59+
print(row)
60+
61+
if not page_token:
62+
break
63+
64+
65+
def authenticate_and_query(project, query, launch_browser=True):
66+
appflow = flow.InstalledAppFlow.from_client_secrets_file(
67+
'client_secrets.json',
68+
scopes=['https://www.googleapis.com/auth/bigquery'])
69+
70+
if launch_browser:
71+
appflow.run_local_server()
72+
else:
73+
appflow.run_console()
74+
75+
run_query(appflow.credentials, project, query)
76+
77+
78+
if __name__ == '__main__':
79+
parser = argparse.ArgumentParser(
80+
description=__doc__,
81+
formatter_class=argparse.RawDescriptionHelpFormatter)
82+
parser.add_argument(
83+
'--launch-browser',
84+
help='Use a local server flow to authenticate. ',
85+
action='store_true')
86+
parser.add_argument('project', help='Project to use for BigQuery billing.')
87+
parser.add_argument('query', help='BigQuery SQL Query.')
88+
89+
args = parser.parse_args()
90+
91+
authenticate_and_query(
92+
args.project, args.query, launch_browser=args.launch_browser)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2017 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
import google.auth
18+
import mock
19+
import pytest
20+
21+
from user_credentials import authenticate_and_query
22+
23+
24+
PROJECT = os.environ['GCLOUD_PROJECT']
25+
26+
27+
@pytest.fixture
28+
def mock_flow():
29+
flow_patch = mock.patch(
30+
'google_auth_oauthlib.flow.InstalledAppFlow', autospec=True)
31+
32+
with flow_patch as flow_mock:
33+
flow_mock.from_client_secrets_file.return_value = flow_mock
34+
flow_mock.credentials = google.auth.default()[0]
35+
yield flow_mock
36+
37+
38+
def test_auth_query_console(mock_flow, capsys):
39+
authenticate_and_query(PROJECT, 'SELECT 1+1;', launch_browser=False)
40+
out, _ = capsys.readouterr()
41+
assert '2' in out

0 commit comments

Comments
 (0)