From ba3109d015ea53bffd9b95d5f25562cd6a9f761c Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 7 Aug 2015 15:51:27 -0400 Subject: [PATCH 1/9] Add 'Dataset.access_grants' property. Marshal access grants to/from dataset API requests/responses. --- gcloud/bigquery/dataset.py | 87 +++++++++++++++++++++++++++++- gcloud/bigquery/test_dataset.py | 93 ++++++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 2 deletions(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index db5cfb70f216..73148c35382e 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -20,6 +20,29 @@ from gcloud.bigquery.table import Table +class AccessGrant(object): + """Represent grant of an access role to an entity. + + :type role: string (one of 'OWNER', 'WRITER', 'READER'). + :param role: role granted to the entity. + + :type entity_type: string (one of 'specialGroup', 'groupByEmail', or + 'userByEmail') + :param entity_type: type of entity being granted the role. + + :type entity_id: string + :param entity_id: ID of entity being granted the role. + """ + def __init__(self, role, entity_type, entity_id): + self.role = role + self.entity_type = entity_type + self.entity_id = entity_id + + def __repr__(self): # pragma: NO COVER + return '' % ( + self.role, self.entity_type, self.entity_id) + + class Dataset(object): """Datasets are containers for tables. @@ -32,12 +55,16 @@ class Dataset(object): :type client: :class:`gcloud.bigquery.client.Client` :param client: A client which holds credentials and project configuration for the dataset (which requires a project). + + :type access_grants: list of :class:`AccessGrant` + :param access_grants: roles granted to entities for this dataset """ - def __init__(self, name, client): + def __init__(self, name, client, access_grants=()): self.name = name self._client = client self._properties = {} + self.access_grants = access_grants @property def project(self): @@ -57,6 +84,29 @@ def path(self): """ return '/projects/%s/datasets/%s' % (self.project, self.name) + @property + def access_grants(self): + """Dataset's access grants. + + :rtype: list of :class:`AccessGrant` + :returns: roles granted to entities for this dataset + """ + return list(self._access_roles) + + @access_grants.setter + def access_grants(self, value): + """Update dataset's access grants + + :type value: list of :class:`AccessGrant` + :param value: roles granted to entities for this dataset + + :raises: TypeError if 'value' is not a sequence, or ValueError if + any item in the sequence is not an AccessGrant + """ + if not all(isinstance(field, AccessGrant) for field in value): + raise ValueError('Values must be AccessGrant instances') + self._access_roles = tuple(value) + @property def created(self): """Datetime at which the dataset was created. @@ -227,6 +277,28 @@ def _require_client(self, client): client = self._client return client + def _parse_access_grants(self, access): + """Parse a resource fragment into a schema field. + + :type access: list of mappings + :param access: each mapping represents a single access grant + + :rtype: list of :class:`AccessGrant` + :returns: a list of parsed grants + """ + result = [] + for grant in access: + role = grant['role'] + if 'specialGroup' in grant: + entity_type = 'specialGroup' + elif 'groupByEmail' in grant: + entity_type = 'groupByEmail' + else: + entity_type = 'userByEmail' + result.append( + AccessGrant(role, entity_type, grant[entity_type])) + return result + def _set_properties(self, api_response): """Update properties from resource in body of ``api_response`` @@ -235,12 +307,22 @@ def _set_properties(self, api_response): """ self._properties.clear() cleaned = api_response.copy() + access = cleaned.pop('access', ()) + self.access_grants = self._parse_access_grants(access) if 'creationTime' in cleaned: cleaned['creationTime'] = float(cleaned['creationTime']) if 'lastModifiedTime' in cleaned: cleaned['lastModifiedTime'] = float(cleaned['lastModifiedTime']) self._properties.update(cleaned) + def _build_access_resource(self): + """Generate a resource fragment for dataset's access grants.""" + result = [] + for grant in self.access_grants: + info = {'role': grant.role, grant.entity_type: grant.entity_id} + result.append(info) + return result + def _build_resource(self): """Generate a resource for ``create`` or ``update``.""" resource = { @@ -260,6 +342,9 @@ def _build_resource(self): if self.location is not None: resource['location'] = self.location + if len(self.access_grants) > 0: + resource['access'] = self._build_access_resource() + return resource def create(self, client=None): diff --git a/gcloud/bigquery/test_dataset.py b/gcloud/bigquery/test_dataset.py index 1d65c0e1318f..cd7615665e03 100644 --- a/gcloud/bigquery/test_dataset.py +++ b/gcloud/bigquery/test_dataset.py @@ -15,6 +15,22 @@ import unittest2 +class TestAccessRole(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.bigquery.dataset import AccessGrant + return AccessGrant + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor_defaults(self): + grant = self._makeOne('OWNER', 'userByEmail', 'phred@example.com') + self.assertEqual(grant.role, 'OWNER') + self.assertEqual(grant.entity_type, 'userByEmail') + self.assertEqual(grant.entity_id, 'phred@example.com') + + class TestDataset(unittest2.TestCase): PROJECT = 'project' DS_NAME = 'dataset-name' @@ -39,6 +55,8 @@ def _setUpConstants(self): def _makeResource(self): self._setUpConstants() + USER_EMAIL = 'phred@example.com' + GROUP_EMAIL = 'group-name@lists.example.com' return { 'creationTime': self.WHEN_TS * 1000, 'datasetReference': @@ -48,10 +66,37 @@ def _makeResource(self): 'lastModifiedTime': self.WHEN_TS * 1000, 'location': 'US', 'selfLink': self.RESOURCE_URL, + 'access': [ + {'role': 'OWNER', 'userByEmail': USER_EMAIL}, + {'role': 'OWNER', 'groupByEmail': GROUP_EMAIL}, + {'role': 'WRITER', 'specialGroup': 'projectWriters'}, + {'role': 'READER', 'specialGroup': 'projectReaders'}], } - def _verifyResourceProperties(self, dataset, resource): + def _verifyAccessGrants(self, access_grants, resource): + r_grants = resource['access'] + self.assertEqual(len(access_grants), len(r_grants)) + + for a_grant, r_grant in zip(access_grants, r_grants): + + self.assertEqual(a_grant.role, r_grant['role']) + + if 'specialGroup' in r_grant: + self.assertEqual(a_grant.entity_type, 'specialGroup') + entity_id = r_grant['specialGroup'] + elif 'groupByEmail' in r_grant: + self.assertEqual(a_grant.entity_type, 'groupByEmail') + entity_id = r_grant['groupByEmail'] + else: + self.assertEqual(a_grant.entity_type, 'userByEmail') + entity_id = r_grant['userByEmail'] + + self.assertEqual(a_grant.entity_id, entity_id) + + def _verifyReadonlyResourceProperties(self, dataset, resource): + self.assertEqual(dataset.dataset_id, self.DS_ID) + if 'creationTime' in resource: self.assertEqual(dataset.created, self.WHEN) else: @@ -69,12 +114,21 @@ def _verifyResourceProperties(self, dataset, resource): else: self.assertEqual(dataset.self_link, None) + def _verifyResourceProperties(self, dataset, resource): + + self._verifyReadonlyResourceProperties(dataset, resource) + self.assertEqual(dataset.default_table_expiration_ms, resource.get('defaultTableExpirationMs')) self.assertEqual(dataset.description, resource.get('description')) self.assertEqual(dataset.friendly_name, resource.get('friendlyName')) self.assertEqual(dataset.location, resource.get('location')) + if 'access' in resource: + self._verifyAccessGrants(dataset.access_grants, resource) + else: + self.assertEqual(dataset.access_grants, []) + def test_ctor(self): client = _Client(self.PROJECT) dataset = self._makeOne(self.DS_NAME, client) @@ -84,6 +138,7 @@ def test_ctor(self): self.assertEqual( dataset.path, '/projects/%s/datasets/%s' % (self.PROJECT, self.DS_NAME)) + self.assertEqual(dataset.access_grants, []) self.assertEqual(dataset.created, None) self.assertEqual(dataset.dataset_id, None) @@ -96,6 +151,29 @@ def test_ctor(self): self.assertEqual(dataset.friendly_name, None) self.assertEqual(dataset.location, None) + def test_access_roles_setter_non_list(self): + client = _Client(self.PROJECT) + dataset = self._makeOne(self.DS_NAME, client) + with self.assertRaises(TypeError): + dataset.access_grants = object() + + def test_access_roles_setter_invalid_field(self): + from gcloud.bigquery.dataset import AccessGrant + client = _Client(self.PROJECT) + dataset = self._makeOne(self.DS_NAME, client) + phred = AccessGrant('OWNER', 'userByEmail', 'phred@example.com') + with self.assertRaises(ValueError): + dataset.access_grants = [phred, object()] + + def test_access_roles_setter(self): + from gcloud.bigquery.dataset import AccessGrant + client = _Client(self.PROJECT) + dataset = self._makeOne(self.DS_NAME, client) + phred = AccessGrant('OWNER', 'userByEmail', 'phred@example.com') + bharney = AccessGrant('OWNER', 'userByEmail', 'bharney@example.com') + dataset.access_grants = [phred, bharney] + self.assertEqual(dataset.access_grants, [phred, bharney]) + def test_default_table_expiration_ms_setter_bad_value(self): client = _Client(self.PROJECT) dataset = self._makeOne(self.DS_NAME, client) @@ -196,7 +274,10 @@ def test_create_w_bound_client(self): self._verifyResourceProperties(dataset, RESOURCE) def test_create_w_alternate_client(self): + from gcloud.bigquery.dataset import AccessGrant PATH = 'projects/%s/datasets' % self.PROJECT + USER_EMAIL = 'phred@example.com' + GROUP_EMAIL = 'group-name@lists.example.com' DESCRIPTION = 'DESCRIPTION' TITLE = 'TITLE' RESOURCE = self._makeResource() @@ -209,6 +290,11 @@ def test_create_w_alternate_client(self): dataset = self._makeOne(self.DS_NAME, client=CLIENT1) dataset.friendly_name = TITLE dataset.description = DESCRIPTION + dataset.access_grants = [ + AccessGrant('OWNER', 'userByEmail', USER_EMAIL), + AccessGrant('OWNER', 'groupByEmail', GROUP_EMAIL), + AccessGrant('READER', 'specialGroup', 'projectReaders'), + AccessGrant('WRITER', 'specialGroup', 'projectWriters')] dataset.create(client=CLIENT2) @@ -222,6 +308,11 @@ def test_create_w_alternate_client(self): {'projectId': self.PROJECT, 'datasetId': self.DS_NAME}, 'description': DESCRIPTION, 'friendlyName': TITLE, + 'access': [ + {'role': 'OWNER', 'userByEmail': USER_EMAIL}, + {'role': 'OWNER', 'groupByEmail': GROUP_EMAIL}, + {'role': 'READER', 'specialGroup': 'projectReaders'}, + {'role': 'WRITER', 'specialGroup': 'projectWriters'}], } self.assertEqual(req['data'], SENT) self._verifyResourceProperties(dataset, RESOURCE) From e2fa8e473861352367eaad2a567964ff6441d17f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 8 Aug 2015 12:40:02 -0400 Subject: [PATCH 2/9] .coveragerc already disables '__repr__'. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36572026 --- gcloud/bigquery/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index 73148c35382e..ea704878f5a2 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -38,7 +38,7 @@ def __init__(self, role, entity_type, entity_id): self.entity_type = entity_type self.entity_id = entity_id - def __repr__(self): # pragma: NO COVER + def __repr__(self): return '' % ( self.role, self.entity_type, self.entity_id) From cc746f5c7cd6b2aa14108f536155ebc67d0ffb41 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 8 Aug 2015 12:45:04 -0400 Subject: [PATCH 3/9] Fix testcase class name after rename. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36572171 --- gcloud/bigquery/test_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcloud/bigquery/test_dataset.py b/gcloud/bigquery/test_dataset.py index cd7615665e03..66d204b6027f 100644 --- a/gcloud/bigquery/test_dataset.py +++ b/gcloud/bigquery/test_dataset.py @@ -15,7 +15,7 @@ import unittest2 -class TestAccessRole(unittest2.TestCase): +class TestAccessGrant(unittest2.TestCase): def _getTargetClass(self): from gcloud.bigquery.dataset import AccessGrant From 786288d0ef7070e77948c5616ef026e8b2fd63b7 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 8 Aug 2015 12:46:14 -0400 Subject: [PATCH 4/9] Fix backing attribute name after rename. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36572180 --- gcloud/bigquery/dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index ea704878f5a2..747be8419908 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -91,7 +91,7 @@ def access_grants(self): :rtype: list of :class:`AccessGrant` :returns: roles granted to entities for this dataset """ - return list(self._access_roles) + return list(self._access_grants) @access_grants.setter def access_grants(self, value): @@ -105,7 +105,7 @@ def access_grants(self, value): """ if not all(isinstance(field, AccessGrant) for field in value): raise ValueError('Values must be AccessGrant instances') - self._access_roles = tuple(value) + self._access_grants = tuple(value) @property def created(self): From e72948c7478ceb6e4a4d5080ce30548bc95cc4d2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 10 Aug 2015 13:38:24 -0400 Subject: [PATCH 5/9] Fix copy-pasta in docstring summary line. --- gcloud/bigquery/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index 747be8419908..972757e48e2b 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -278,7 +278,7 @@ def _require_client(self, client): return client def _parse_access_grants(self, access): - """Parse a resource fragment into a schema field. + """Parse a resource fragment into a set of access grants. :type access: list of mappings :param access: each mapping represents a single access grant From 9c5d6dcbf7f7c6cca37a50b8ebf46d15b48bcd81 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 10 Aug 2015 13:55:58 -0400 Subject: [PATCH 6/9] Don't assume back-end will return only documented entity types. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36658251 --- gcloud/bigquery/dataset.py | 10 +++------- gcloud/bigquery/test_dataset.py | 33 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index 972757e48e2b..d9debdbad5a8 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -288,13 +288,9 @@ def _parse_access_grants(self, access): """ result = [] for grant in access: - role = grant['role'] - if 'specialGroup' in grant: - entity_type = 'specialGroup' - elif 'groupByEmail' in grant: - entity_type = 'groupByEmail' - else: - entity_type = 'userByEmail' + grant = grant.copy() + role = grant.pop('role') + entity_type, entity_id = grant.items()[0] result.append( AccessGrant(role, entity_type, grant[entity_type])) return result diff --git a/gcloud/bigquery/test_dataset.py b/gcloud/bigquery/test_dataset.py index 66d204b6027f..a75af0230100 100644 --- a/gcloud/bigquery/test_dataset.py +++ b/gcloud/bigquery/test_dataset.py @@ -78,19 +78,11 @@ def _verifyAccessGrants(self, access_grants, resource): self.assertEqual(len(access_grants), len(r_grants)) for a_grant, r_grant in zip(access_grants, r_grants): - - self.assertEqual(a_grant.role, r_grant['role']) - - if 'specialGroup' in r_grant: - self.assertEqual(a_grant.entity_type, 'specialGroup') - entity_id = r_grant['specialGroup'] - elif 'groupByEmail' in r_grant: - self.assertEqual(a_grant.entity_type, 'groupByEmail') - entity_id = r_grant['groupByEmail'] - else: - self.assertEqual(a_grant.entity_type, 'userByEmail') - entity_id = r_grant['userByEmail'] - + role = r_grant.pop('role') + self.assertEqual(a_grant.role, role) + self.assertEqual(len(r_grant), 1) + entity_type, entity_id = r_grant.items()[0] + self.assertEqual(a_grant.entity_type, entity_type) self.assertEqual(a_grant.entity_id, entity_id) def _verifyReadonlyResourceProperties(self, dataset, resource): @@ -253,6 +245,21 @@ def test_from_api_repr_w_properties(self): self.assertTrue(dataset._client is client) self._verifyResourceProperties(dataset, RESOURCE) + def test__parse_access_grants_w_unknown_entity_type(self): + USER_EMAIL = 'phred@example.com' + GROUP_EMAIL = 'group-name@lists.example.com' + RESOURCE = { + 'access': [ + {'role': 'OWNER', 'userByEmail': USER_EMAIL}, + {'role': 'WRITER', 'groupByEmail': GROUP_EMAIL}, + {'role': 'READER', 'specialGroup': 'projectReaders'}, + {'role': 'READER', 'unknown': 'UNKNOWN'}] + } + client = _Client(self.PROJECT) + dataset = self._makeOne(self.DS_NAME, client=client) + grants = dataset._parse_access_grants(RESOURCE['access']) + self._verifyAccessGrants(grants, RESOURCE) + def test_create_w_bound_client(self): PATH = 'projects/%s/datasets' % self.PROJECT RESOURCE = self._makeResource() From f7fdc6fbf921b8f3abb79d2352e02cec68a702a0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 10 Aug 2015 14:01:09 -0400 Subject: [PATCH 7/9] Py3k / lint fixes. --- gcloud/bigquery/dataset.py | 4 ++-- gcloud/bigquery/test_dataset.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index d9debdbad5a8..62e95b0f6b5b 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -290,9 +290,9 @@ def _parse_access_grants(self, access): for grant in access: grant = grant.copy() role = grant.pop('role') - entity_type, entity_id = grant.items()[0] + entity_type, entity_id = list(grant.items())[0] result.append( - AccessGrant(role, entity_type, grant[entity_type])) + AccessGrant(role, entity_type, entity_id)) return result def _set_properties(self, api_response): diff --git a/gcloud/bigquery/test_dataset.py b/gcloud/bigquery/test_dataset.py index a75af0230100..69302a8119cd 100644 --- a/gcloud/bigquery/test_dataset.py +++ b/gcloud/bigquery/test_dataset.py @@ -81,7 +81,7 @@ def _verifyAccessGrants(self, access_grants, resource): role = r_grant.pop('role') self.assertEqual(a_grant.role, role) self.assertEqual(len(r_grant), 1) - entity_type, entity_id = r_grant.items()[0] + entity_type, entity_id = list(r_grant.items())[0] self.assertEqual(a_grant.entity_type, entity_type) self.assertEqual(a_grant.entity_id, entity_id) From 09c1d14a44f16b454125e0a93c734c2ec153aad0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 11 Aug 2015 13:00:38 -0400 Subject: [PATCH 8/9] Work around hypothetical case parsing 'dataset.access_grants': The back-end might return a grant with more than one entity_type for a given role. See: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 --- gcloud/bigquery/dataset.py | 9 +++++--- gcloud/bigquery/test_dataset.py | 39 ++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index 62e95b0f6b5b..33ed9c8d718f 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -290,9 +290,12 @@ def _parse_access_grants(self, access): for grant in access: grant = grant.copy() role = grant.pop('role') - entity_type, entity_id = list(grant.items())[0] - result.append( - AccessGrant(role, entity_type, entity_id)) + # Hypothetical case: we don't know that the back-end will ever + # return such structures, but they are logical. See: + #https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 + for entity_type, entity_id in grant.items(): + result.append( + AccessGrant(role, entity_type, entity_id)) return result def _set_properties(self, api_response): diff --git a/gcloud/bigquery/test_dataset.py b/gcloud/bigquery/test_dataset.py index 69302a8119cd..d4c3fa94af11 100644 --- a/gcloud/bigquery/test_dataset.py +++ b/gcloud/bigquery/test_dataset.py @@ -74,16 +74,19 @@ def _makeResource(self): } def _verifyAccessGrants(self, access_grants, resource): - r_grants = resource['access'] - self.assertEqual(len(access_grants), len(r_grants)) + r_grants = [] + for r_grant in resource['access']: + role = r_grant.pop('role') + for entity_type, entity_id in r_grant.items(): + r_grants.append({'role': role, + 'entity_type': entity_type, + 'entity_id': entity_id}) + self.assertEqual(len(access_grants), len(r_grants)) for a_grant, r_grant in zip(access_grants, r_grants): - role = r_grant.pop('role') - self.assertEqual(a_grant.role, role) - self.assertEqual(len(r_grant), 1) - entity_type, entity_id = list(r_grant.items())[0] - self.assertEqual(a_grant.entity_type, entity_type) - self.assertEqual(a_grant.entity_id, entity_id) + self.assertEqual(a_grant.role, r_grant['role']) + self.assertEqual(a_grant.entity_type, r_grant['entity_type']) + self.assertEqual(a_grant.entity_id, r_grant['entity_id']) def _verifyReadonlyResourceProperties(self, dataset, resource): @@ -260,6 +263,26 @@ def test__parse_access_grants_w_unknown_entity_type(self): grants = dataset._parse_access_grants(RESOURCE['access']) self._verifyAccessGrants(grants, RESOURCE) + def test__parse_access_grants_w_multiple_entity_types(self): + # Hypothetical case: we don't know that the back-end will ever + # return such structures, but they are logical. See: + #https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 + USER_EMAIL = 'phred@example.com' + OTHER_EMAIL = 'bharney@example.com' + GROUP_EMAIL = 'group-name@lists.example.com' + RESOURCE = { + 'access': [ + {'role': 'OWNER', 'userByEmail': USER_EMAIL}, + {'role': 'WRITER', 'groupByEmail': GROUP_EMAIL}, + {'role': 'READER', + 'specialGroup': 'projectReaders', + 'userByEmail': OTHER_EMAIL}] + } + client = _Client(self.PROJECT) + dataset = self._makeOne(self.DS_NAME, client=client) + grants = dataset._parse_access_grants(RESOURCE['access']) + self._verifyAccessGrants(grants, RESOURCE) + def test_create_w_bound_client(self): PATH = 'projects/%s/datasets' % self.PROJECT RESOURCE = self._makeResource() From 6720b3577415977b3378d4a5b803570bd8dae6cd Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 11 Aug 2015 14:34:05 -0400 Subject: [PATCH 9/9] Defend against hash randomization non-determanism. Also, de-lint long URL comments. --- gcloud/bigquery/dataset.py | 4 ++-- gcloud/bigquery/test_dataset.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gcloud/bigquery/dataset.py b/gcloud/bigquery/dataset.py index 33ed9c8d718f..47d799e33186 100644 --- a/gcloud/bigquery/dataset.py +++ b/gcloud/bigquery/dataset.py @@ -292,8 +292,8 @@ def _parse_access_grants(self, access): role = grant.pop('role') # Hypothetical case: we don't know that the back-end will ever # return such structures, but they are logical. See: - #https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 - for entity_type, entity_id in grant.items(): + # https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 + for entity_type, entity_id in sorted(grant.items()): result.append( AccessGrant(role, entity_type, entity_id)) return result diff --git a/gcloud/bigquery/test_dataset.py b/gcloud/bigquery/test_dataset.py index d4c3fa94af11..3edeb0432f5a 100644 --- a/gcloud/bigquery/test_dataset.py +++ b/gcloud/bigquery/test_dataset.py @@ -77,7 +77,7 @@ def _verifyAccessGrants(self, access_grants, resource): r_grants = [] for r_grant in resource['access']: role = r_grant.pop('role') - for entity_type, entity_id in r_grant.items(): + for entity_type, entity_id in sorted(r_grant.items()): r_grants.append({'role': role, 'entity_type': entity_type, 'entity_id': entity_id}) @@ -266,7 +266,7 @@ def test__parse_access_grants_w_unknown_entity_type(self): def test__parse_access_grants_w_multiple_entity_types(self): # Hypothetical case: we don't know that the back-end will ever # return such structures, but they are logical. See: - #https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 + # https://github.com/GoogleCloudPlatform/gcloud-python/pull/1046#discussion_r36687769 USER_EMAIL = 'phred@example.com' OTHER_EMAIL = 'bharney@example.com' GROUP_EMAIL = 'group-name@lists.example.com'