From a81591518d168001c3182fd834842676ccb19491 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 6 Sep 2016 18:26:17 -0400 Subject: [PATCH 1/2] Add 'Client.list_projects'. Closes #2143. --- google/cloud/bigquery/client.py | 60 +++++++++++++++++++++++++++++ unit_tests/bigquery/test_client.py | 61 ++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/google/cloud/bigquery/client.py b/google/cloud/bigquery/client.py index d5560329ee5d..0ae04f1a5254 100644 --- a/google/cloud/bigquery/client.py +++ b/google/cloud/bigquery/client.py @@ -25,6 +25,30 @@ from google.cloud.bigquery.query import QueryResults +class Project(object): + """Wrapper for resource describing a BigQuery project. + + :type project_id: str + :param project_id: Opaque ID of the project + + :type numeric_id: int + :param numeric_id: Numeric ID of the project + + :type friendly_name: str + :param friendly_name: Display name of the project + """ + def __init__(self, project_id, numeric_id, friendly_name): + self.project_id = project_id + self.numeric_id = numeric_id + self.friendly_name = friendly_name + + @classmethod + def from_api_repr(cls, resource): + """Factory: construct an instance from a resource dict.""" + return cls( + resource['id'], resource['numericId'], resource['friendlyName']) + + class Client(JSONClient): """Client to bundle configuration needed for API requests. @@ -48,6 +72,42 @@ class Client(JSONClient): _connection_class = Connection + def list_projects(self, max_results=None, page_token=None): + """List projects for the project associated with this client. + + See: + https://cloud.google.com/bigquery/docs/reference/v2/projects/list + + :type max_results: int + :param max_results: maximum number of projects to return, If not + passed, defaults to a value set by the API. + + :type page_token: str + :param page_token: opaque marker for the next "page" of projects. If + not passed, the API will return the first page of + projects. + + :rtype: tuple, (list, str) + :returns: list of :class:`gcloud.bigquery.client.Project`, plus a + "next page token" string: if the token is not None, + indicates that more projects can be retrieved with another + call (pass that value as ``page_token``). + """ + params = {} + + if max_results is not None: + params['maxResults'] = max_results + + if page_token is not None: + params['pageToken'] = page_token + + path = '/projects' + resp = self.connection.api_request(method='GET', path=path, + query_params=params) + projects = [Project.from_api_repr(resource) + for resource in resp.get('projects', ())] + return projects, resp.get('nextPageToken') + def list_datasets(self, include_all=False, max_results=None, page_token=None): """List datasets for the project associated with this client. diff --git a/unit_tests/bigquery/test_client.py b/unit_tests/bigquery/test_client.py index 8057115794a3..44448d9deab6 100644 --- a/unit_tests/bigquery/test_client.py +++ b/unit_tests/bigquery/test_client.py @@ -34,6 +34,67 @@ def test_ctor(self): self.assertTrue(client.connection.credentials is creds) self.assertTrue(client.connection.http is http) + def test_list_projects_defaults(self): + from google.cloud.bigquery.client import Project + PROJECT_1 = 'PROJECT_ONE' + PROJECT_2 = 'PROJECT_TWO' + PATH = 'projects' + TOKEN = 'TOKEN' + DATA = { + 'nextPageToken': TOKEN, + 'projects': [ + {'kind': 'bigquery#project', + 'id': PROJECT_1, + 'numericId': 1, + 'projectReference': {'projectId': PROJECT_1}, + 'friendlyName': 'One'}, + {'kind': 'bigquery#project', + 'id': PROJECT_2, + 'numericId': 2, + 'projectReference': {'projectId': PROJECT_2}, + 'friendlyName': 'Two'}, + ] + } + creds = _Credentials() + client = self._makeOne(PROJECT_1, creds) + conn = client.connection = _Connection(DATA) + + projects, token = client.list_projects() + + self.assertEqual(len(projects), len(DATA['projects'])) + for found, expected in zip(projects, DATA['projects']): + self.assertTrue(isinstance(found, Project)) + self.assertEqual(found.project_id, expected['id']) + self.assertEqual(found.numeric_id, expected['numericId']) + self.assertEqual(found.friendly_name, expected['friendlyName']) + self.assertEqual(token, TOKEN) + + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/%s' % PATH) + + def test_list_projects_explicit_response_missing_projects_key(self): + PROJECT = 'PROJECT' + PATH = 'projects' + TOKEN = 'TOKEN' + DATA = {} + creds = _Credentials() + client = self._makeOne(PROJECT, creds) + conn = client.connection = _Connection(DATA) + + projects, token = client.list_projects(max_results=3, page_token=TOKEN) + + self.assertEqual(len(projects), 0) + self.assertEqual(token, None) + + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['query_params'], + {'maxResults': 3, 'pageToken': TOKEN}) + def test_list_datasets_defaults(self): from google.cloud.bigquery.dataset import Dataset PROJECT = 'PROJECT' From 2f0667d7e04323b5dcebcdb9a1ddad6337e61ed7 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 7 Sep 2016 12:08:23 -0400 Subject: [PATCH 2/2] Resolve ambiguous Sphinx references. --- google/cloud/resource_manager/client.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/google/cloud/resource_manager/client.py b/google/cloud/resource_manager/client.py index 95e1224c3384..18641f6b857b 100644 --- a/google/cloud/resource_manager/client.py +++ b/google/cloud/resource_manager/client.py @@ -49,11 +49,12 @@ class Client(BaseClient): _connection_class = Connection def new_project(self, project_id, name=None, labels=None): - """Create a :class:`.Project` bound to the current client. + """Create a project bound to the current client. Use :meth:`Project.reload() \ ` to retrieve - project metadata after creating a :class:`.Project` instance. + project metadata after creating a + :class:`~gcloud.resource_manager.project.Project` instance. .. note: @@ -68,9 +69,10 @@ def new_project(self, project_id, name=None, labels=None): :type labels: dict :param labels: A list of labels associated with the project. - :rtype: :class:`.Project` - :returns: A new instance of a :class:`.Project` **without** - any metadata loaded. + :rtype: :class:`~gcloud.resource_manager.project.Project` + :returns: A new instance of a + :class:`~gcloud.resource_manager.project.Project` + **without** any metadata loaded. """ return Project(project_id=project_id, client=self, name=name, labels=labels) @@ -86,8 +88,9 @@ def fetch_project(self, project_id): :type project_id: str :param project_id: The ID for this project. - :rtype: :class:`.Project` - :returns: A :class:`.Project` with metadata fetched from the API. + :rtype: :class:`~gcloud.resource_manager.project.Project` + :returns: A :class:`~gcloud.resource_manager.project.Project` with + metadata fetched from the API. """ project = self.new_project(project_id) project.reload() @@ -142,7 +145,7 @@ def list_projects(self, filter_params=None, page_size=None): :returns: A project iterator. The iterator will make multiple API requests if you continue iterating and there are more pages of results. Each item returned will be a. - :class:`.Project`. + :class:`~gcloud.resource_manager.project.Project`. """ extra_params = {} @@ -175,7 +178,7 @@ def __init__(self, client, extra_params=None): extra_params=extra_params) def get_items_from_response(self, response): - """Yield :class:`.Project` items from response. + """Yield projects from response. :type response: dict :param response: The JSON API response for a page of projects.