Skip to content

Commit 92219b8

Browse files
authored
Merge pull request #2254 from tseaver/2143-bigquery-list-projects
Add 'Client.list_projects'.
2 parents 59b2510 + 2f0667d commit 92219b8

3 files changed

Lines changed: 133 additions & 9 deletions

File tree

google/cloud/bigquery/client.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@
2525
from google.cloud.bigquery.query import QueryResults
2626

2727

28+
class Project(object):
29+
"""Wrapper for resource describing a BigQuery project.
30+
31+
:type project_id: str
32+
:param project_id: Opaque ID of the project
33+
34+
:type numeric_id: int
35+
:param numeric_id: Numeric ID of the project
36+
37+
:type friendly_name: str
38+
:param friendly_name: Display name of the project
39+
"""
40+
def __init__(self, project_id, numeric_id, friendly_name):
41+
self.project_id = project_id
42+
self.numeric_id = numeric_id
43+
self.friendly_name = friendly_name
44+
45+
@classmethod
46+
def from_api_repr(cls, resource):
47+
"""Factory: construct an instance from a resource dict."""
48+
return cls(
49+
resource['id'], resource['numericId'], resource['friendlyName'])
50+
51+
2852
class Client(JSONClient):
2953
"""Client to bundle configuration needed for API requests.
3054
@@ -48,6 +72,42 @@ class Client(JSONClient):
4872

4973
_connection_class = Connection
5074

75+
def list_projects(self, max_results=None, page_token=None):
76+
"""List projects for the project associated with this client.
77+
78+
See:
79+
https://cloud.google.com/bigquery/docs/reference/v2/projects/list
80+
81+
:type max_results: int
82+
:param max_results: maximum number of projects to return, If not
83+
passed, defaults to a value set by the API.
84+
85+
:type page_token: str
86+
:param page_token: opaque marker for the next "page" of projects. If
87+
not passed, the API will return the first page of
88+
projects.
89+
90+
:rtype: tuple, (list, str)
91+
:returns: list of :class:`gcloud.bigquery.client.Project`, plus a
92+
"next page token" string: if the token is not None,
93+
indicates that more projects can be retrieved with another
94+
call (pass that value as ``page_token``).
95+
"""
96+
params = {}
97+
98+
if max_results is not None:
99+
params['maxResults'] = max_results
100+
101+
if page_token is not None:
102+
params['pageToken'] = page_token
103+
104+
path = '/projects'
105+
resp = self.connection.api_request(method='GET', path=path,
106+
query_params=params)
107+
projects = [Project.from_api_repr(resource)
108+
for resource in resp.get('projects', ())]
109+
return projects, resp.get('nextPageToken')
110+
51111
def list_datasets(self, include_all=False, max_results=None,
52112
page_token=None):
53113
"""List datasets for the project associated with this client.

google/cloud/resource_manager/client.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,12 @@ class Client(BaseClient):
4949
_connection_class = Connection
5050

5151
def new_project(self, project_id, name=None, labels=None):
52-
"""Create a :class:`.Project` bound to the current client.
52+
"""Create a project bound to the current client.
5353
5454
Use :meth:`Project.reload() \
5555
<google.cloud.resource_manager.project.Project.reload>` to retrieve
56-
project metadata after creating a :class:`.Project` instance.
56+
project metadata after creating a
57+
:class:`~gcloud.resource_manager.project.Project` instance.
5758
5859
.. note:
5960
@@ -68,9 +69,10 @@ def new_project(self, project_id, name=None, labels=None):
6869
:type labels: dict
6970
:param labels: A list of labels associated with the project.
7071
71-
:rtype: :class:`.Project`
72-
:returns: A new instance of a :class:`.Project` **without**
73-
any metadata loaded.
72+
:rtype: :class:`~gcloud.resource_manager.project.Project`
73+
:returns: A new instance of a
74+
:class:`~gcloud.resource_manager.project.Project`
75+
**without** any metadata loaded.
7476
"""
7577
return Project(project_id=project_id,
7678
client=self, name=name, labels=labels)
@@ -86,8 +88,9 @@ def fetch_project(self, project_id):
8688
:type project_id: str
8789
:param project_id: The ID for this project.
8890
89-
:rtype: :class:`.Project`
90-
:returns: A :class:`.Project` with metadata fetched from the API.
91+
:rtype: :class:`~gcloud.resource_manager.project.Project`
92+
:returns: A :class:`~gcloud.resource_manager.project.Project` with
93+
metadata fetched from the API.
9194
"""
9295
project = self.new_project(project_id)
9396
project.reload()
@@ -142,7 +145,7 @@ def list_projects(self, filter_params=None, page_size=None):
142145
:returns: A project iterator. The iterator will make multiple API
143146
requests if you continue iterating and there are more
144147
pages of results. Each item returned will be a.
145-
:class:`.Project`.
148+
:class:`~gcloud.resource_manager.project.Project`.
146149
"""
147150
extra_params = {}
148151

@@ -175,7 +178,7 @@ def __init__(self, client, extra_params=None):
175178
extra_params=extra_params)
176179

177180
def get_items_from_response(self, response):
178-
"""Yield :class:`.Project` items from response.
181+
"""Yield projects from response.
179182
180183
:type response: dict
181184
:param response: The JSON API response for a page of projects.

unit_tests/bigquery/test_client.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,67 @@ def test_ctor(self):
3434
self.assertTrue(client.connection.credentials is creds)
3535
self.assertTrue(client.connection.http is http)
3636

37+
def test_list_projects_defaults(self):
38+
from google.cloud.bigquery.client import Project
39+
PROJECT_1 = 'PROJECT_ONE'
40+
PROJECT_2 = 'PROJECT_TWO'
41+
PATH = 'projects'
42+
TOKEN = 'TOKEN'
43+
DATA = {
44+
'nextPageToken': TOKEN,
45+
'projects': [
46+
{'kind': 'bigquery#project',
47+
'id': PROJECT_1,
48+
'numericId': 1,
49+
'projectReference': {'projectId': PROJECT_1},
50+
'friendlyName': 'One'},
51+
{'kind': 'bigquery#project',
52+
'id': PROJECT_2,
53+
'numericId': 2,
54+
'projectReference': {'projectId': PROJECT_2},
55+
'friendlyName': 'Two'},
56+
]
57+
}
58+
creds = _Credentials()
59+
client = self._makeOne(PROJECT_1, creds)
60+
conn = client.connection = _Connection(DATA)
61+
62+
projects, token = client.list_projects()
63+
64+
self.assertEqual(len(projects), len(DATA['projects']))
65+
for found, expected in zip(projects, DATA['projects']):
66+
self.assertTrue(isinstance(found, Project))
67+
self.assertEqual(found.project_id, expected['id'])
68+
self.assertEqual(found.numeric_id, expected['numericId'])
69+
self.assertEqual(found.friendly_name, expected['friendlyName'])
70+
self.assertEqual(token, TOKEN)
71+
72+
self.assertEqual(len(conn._requested), 1)
73+
req = conn._requested[0]
74+
self.assertEqual(req['method'], 'GET')
75+
self.assertEqual(req['path'], '/%s' % PATH)
76+
77+
def test_list_projects_explicit_response_missing_projects_key(self):
78+
PROJECT = 'PROJECT'
79+
PATH = 'projects'
80+
TOKEN = 'TOKEN'
81+
DATA = {}
82+
creds = _Credentials()
83+
client = self._makeOne(PROJECT, creds)
84+
conn = client.connection = _Connection(DATA)
85+
86+
projects, token = client.list_projects(max_results=3, page_token=TOKEN)
87+
88+
self.assertEqual(len(projects), 0)
89+
self.assertEqual(token, None)
90+
91+
self.assertEqual(len(conn._requested), 1)
92+
req = conn._requested[0]
93+
self.assertEqual(req['method'], 'GET')
94+
self.assertEqual(req['path'], '/%s' % PATH)
95+
self.assertEqual(req['query_params'],
96+
{'maxResults': 3, 'pageToken': TOKEN})
97+
3798
def test_list_datasets_defaults(self):
3899
from google.cloud.bigquery.dataset import Dataset
39100
PROJECT = 'PROJECT'

0 commit comments

Comments
 (0)